From 06add20dcd53fdc7ea33a8c413dfbef7fd7a397c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Yag=C3=BCe=20L=C3=B3pez?= Date: Mon, 27 Jul 2026 12:20:42 -0600 Subject: [PATCH 1/9] Remove the "git detached head advice" --- lib/kessel/workflows/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/kessel/workflows/__init__.py b/lib/kessel/workflows/__init__.py index e5117ca..c48f1f9 100644 --- a/lib/kessel/workflows/__init__.py +++ b/lib/kessel/workflows/__init__.py @@ -273,10 +273,9 @@ def load_workflow(name: str) -> Workflow: def git(cmd, cwd=None, check=True) -> None | str: """Run git command and return output, suppressing normal output.""" env = os.environ.copy() - env["GIT_ADVICE_DETACHED_HEAD"] = "false" try: result = subprocess.run( - ["git"] + cmd, + ["git", "-c", "advice.detachedHead=false"] + cmd, cwd=cwd, check=check, stdout=subprocess.PIPE, From d775dfd688bbf2faec9641fd5e916e50d0438e27 Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Tue, 4 Aug 2026 23:14:27 -0600 Subject: [PATCH 2/9] cmake: respect CMAKE_BUILD_PARALLEL_LEVEL when set Signed-off-by: Richard Berger --- lib/kessel/workflows/base/cmake/cmake/build.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/kessel/workflows/base/cmake/cmake/build.sh b/lib/kessel/workflows/base/cmake/cmake/build.sh index 79e08fe..c737c43 100644 --- a/lib/kessel/workflows/base/cmake/cmake/build.sh +++ b/lib/kessel/workflows/base/cmake/cmake/build.sh @@ -15,5 +15,9 @@ if [ -f "$KESSEL_BUILD_ENV" ]; then source "$KESSEL_BUILD_ENV" fi cmake "$@" "$KESSEL_BUILD_DIR" -cmake --build "$KESSEL_BUILD_DIR" --parallel ${KESSEL_CMAKE_TARGETS} +if [ -n "${CMAKE_BUILD_PARALLEL_LEVEL}" ]; then + cmake --build "$KESSEL_BUILD_DIR" ${KESSEL_CMAKE_TARGETS} +else + cmake --build "$KESSEL_BUILD_DIR" --parallel ${KESSEL_CMAKE_TARGETS} +fi ) From f5ddcb93383ef1474c7ed25ed42f3848dbc64324 Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Thu, 13 Aug 2026 23:15:46 -0600 Subject: [PATCH 3/9] cdash: remove hard-coded CTEST_USE_LAUNCHERS Signed-off-by: Richard Berger --- share/kessel/cmake/ctest_driver.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/share/kessel/cmake/ctest_driver.cmake b/share/kessel/cmake/ctest_driver.cmake index e0e8cdb..2130f89 100644 --- a/share/kessel/cmake/ctest_driver.cmake +++ b/share/kessel/cmake/ctest_driver.cmake @@ -22,7 +22,6 @@ set(CTEST_BUILD_CONFIGURATION "RelWithDebInfo") set(CTEST_MODE "$ENV{CTEST_MODE}") set(CTEST_OUTPUT_ON_FAILURE ON) -set(CTEST_USE_LAUNCHERS TRUE) cmake_host_system_information(RESULT NUM_PHYSICAL_CORES QUERY NUMBER_OF_PHYSICAL_CORES) From 27b90d1a8aee0e931faa9819fef620ebf5055fa5 Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Fri, 11 Sep 2026 12:58:22 -0600 Subject: [PATCH 4/9] spack workflow: add common ci script helpers Signed-off-by: Richard Berger --- CHANGELOG.md | 9 +++ .../workflows/base/spack/deployment/use.sh | 58 +++++++++++++++++++ .../templates/spack-project/.gitlab/kessel.sh | 31 +++++----- 3 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 lib/kessel/workflows/base/spack/deployment/use.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index eef5278..e11a414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## Unreleased + +### CI/CD +- Add `workflows/base/spack/deployment/use.sh` with reusable + `kessel_detect_system` and `kessel_activate_deployment` helpers, so projects no + longer copy the system-detection and clone-or-activate logic into their + `.gitlab/kessel.sh`. The spack-project template now sources these from the + deployment's kessel. + ## 0.3.0 ### Spack Integration diff --git a/lib/kessel/workflows/base/spack/deployment/use.sh b/lib/kessel/workflows/base/spack/deployment/use.sh new file mode 100644 index 0000000..ff0e16b --- /dev/null +++ b/lib/kessel/workflows/base/spack/deployment/use.sh @@ -0,0 +1,58 @@ +# © 2026. Triad National Security, LLC. All rights reserved. +# This program was produced under U.S. Government contract 89233218CNA000001 +# for Los Alamos National Laboratory (LANL), which is operated by Triad +# National Security, LLC for the U.S. Department of Energy/National Nuclear +# Security Administration. All rights in the program are reserved by Triad +# National Security, LLC, and the U.S. Department of Energy/National Nuclear +# Security Administration. The Government is granted for itself and others +# acting on its behalf a nonexclusive, paid-up, irrevocable worldwide license +# in this material to reproduce, prepare derivative works, distribute copies to +# the public, perform publicly and display publicly, and to permit others to do +# so. +# +# Helpers for using an existing Spack deployment from a project's CI: detect the +# current system and activate the (possibly cloned) deployment. +# Intended to work on sh, zsh, and bash. + +# Detect the current cluster (KESSEL_SYSTEM) and its scheduler +# (KESSEL_SYSTEM_SCHEDULER). +kessel_detect_system() { + if command -v sacctmgr >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then + KESSEL_SYSTEM=$(sacctmgr list --json clusters | jq -r '.clusters[0].name') + KESSEL_SYSTEM_SCHEDULER="slurm" + elif command -v flux >/dev/null 2>&1; then + KESSEL_SYSTEM=$(hostname | sed 's/[0-9]//g') + KESSEL_SYSTEM_SCHEDULER="flux" + fi + export KESSEL_SYSTEM KESSEL_SYSTEM_SCHEDULER +} + +# Activate the writable workflow deployment (KESSEL_WORKFLOW_DEPLOYMENT), +# cloning it from the cluster deployment (KESSEL_DEPLOYMENT) if needed. Defaults +# to a per-user temp copy; set KESSEL_WORKFLOW_DEPLOYMENT to a path to persist it, +# or to "upstream" to use the (read-only) cluster deployment directly. +kessel_activate_deployment() { + _KESSEL_WORKFLOW_DEPLOYMENT="$KESSEL_WORKFLOW_DEPLOYMENT" + export KESSEL_WORKFLOW_DEPLOYMENT=${KESSEL_WORKFLOW_DEPLOYMENT:-${TMPDIR:-/tmp}/$USER-ci-envs} + + if [ "$KESSEL_WORKFLOW_DEPLOYMENT" = "upstream" ] && [ -d "$KESSEL_DEPLOYMENT" ]; then + source "$KESSEL_DEPLOYMENT/activate.sh" + else + if [ -d "$KESSEL_DEPLOYMENT" ] && [ ! -d "$KESSEL_WORKFLOW_DEPLOYMENT" ]; then + source "$KESSEL_DEPLOYMENT/activate.sh" + echo "Creating writable deployment copy at $KESSEL_WORKFLOW_DEPLOYMENT" + clone-deployment "$KESSEL_WORKFLOW_DEPLOYMENT" > /dev/null + fi + if [ ! -d "$KESSEL_WORKFLOW_DEPLOYMENT" ]; then + echo "ERROR: $KESSEL_WORKFLOW_DEPLOYMENT does not exist!" >&2 + return 1 + elif [ -z "$_KESSEL_WORKFLOW_DEPLOYMENT" ] && [ ! -O "$KESSEL_WORKFLOW_DEPLOYMENT" ]; then + echo "ERROR: $KESSEL_WORKFLOW_DEPLOYMENT not owned by $USER!" >&2 + return 1 + else + source "$KESSEL_WORKFLOW_DEPLOYMENT/activate.sh" + fi + fi + + unset _KESSEL_WORKFLOW_DEPLOYMENT +} diff --git a/share/kessel/templates/spack-project/.gitlab/kessel.sh b/share/kessel/templates/spack-project/.gitlab/kessel.sh index 194eda0..ebb3470 100644 --- a/share/kessel/templates/spack-project/.gitlab/kessel.sh +++ b/share/kessel/templates/spack-project/.gitlab/kessel.sh @@ -1,30 +1,35 @@ +# Pick the KESSEL_DEPLOYMENT for this cluster, then source the shared helpers +# that ship inside the deployment's kessel. +# Intended to work on sh, zsh, and bash. DEPLOYMENT_VERSION="2025-10-21" SCRIPT_PATH=${BASH_SOURCE[0]:-${(%):-%x}} PARENT_DIR="$( cd "$( dirname "${SCRIPT_PATH}" )" &>/dev/null && pwd )" -export FLECSI_CHECKOUT=$(realpath $PARENT_DIR/..) +export PROJECT_CHECKOUT=$(realpath $PARENT_DIR/..) +# Just enough detection to choose the deployment path; kessel_detect_system does +# the rest. if command -v jq >/dev/null 2>&1 && command -v sacctmgr >/dev/null 2>&1; then - SYSTEM_NAME=$(sacctmgr list --json clusters | jq -r '.clusters[0].name') + KESSEL_SYSTEM=$(sacctmgr list --json clusters | jq -r '.clusters[0].name') elif command -v flux >/dev/null 2>&1; then - SYSTEM_NAME=$(hostname | sed 's/[0-9]//g') + KESSEL_SYSTEM=$(hostname | sed 's/[0-9]//g') fi -# During a regular CI run the workflow deployment is always a temporary copy of -# the cluster deployment to allow customization. This can be overwritten by -# setting KESSEL_WORKFLOW_DEPLOYMENT to another persistent location. -export KESSEL_WORKFLOW_DEPLOYMENT="${KESSEL_WORKFLOW_DEPLOYMENT:-${TMPDIR:-/tmp}/$USER-ci-envs}" - -if [ "$SYSTEM_NAME" = "darwin" ]; then +if [ "$KESSEL_SYSTEM" = "darwin" ]; then export KESSEL_DEPLOYMENT=${KESSEL_DEPLOYMENT:-/usr/projects/YOUR_PROJECT/deployments/${DEPLOYMENT_VERSION}} else echo "ERROR: Unknown system!" >&2 return 1 fi -if [ -d "${KESSEL_DEPLOYMENT}" ] && [ ! -d "${KESSEL_WORKFLOW_DEPLOYMENT}" ]; then - source "${KESSEL_DEPLOYMENT}/activate.sh" - clone-deployment "${KESSEL_WORKFLOW_DEPLOYMENT}" +# Source the shared helpers (kessel_detect_system, kessel_activate_deployment). +_KESSEL_DEPLOYMENT_USE="$KESSEL_DEPLOYMENT/kessel/lib/kessel/workflows/base/spack/deployment/use.sh" +if [ ! -r "$_KESSEL_DEPLOYMENT_USE" ]; then + echo "ERROR: $_KESSEL_DEPLOYMENT_USE not found (deployment missing or too old)!" >&2 + return 1 fi +source "$_KESSEL_DEPLOYMENT_USE" +unset _KESSEL_DEPLOYMENT_USE -source "${KESSEL_WORKFLOW_DEPLOYMENT}/activate.sh" +kessel_detect_system +kessel_activate_deployment From b31d78fdf5ef5d4927e02d990ba28f64427361a1 Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Fri, 11 Sep 2026 13:05:43 -0600 Subject: [PATCH 5/9] add configurable alloc line Signed-off-by: Richard Berger --- CHANGELOG.md | 4 ++++ lib/kessel/workflows/__init__.py | 18 ++++++++++++------ lib/kessel/workflows/base/spack/__init__.py | 6 ++++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e11a414..2da5038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ `.gitlab/kessel.sh`. The spack-project template now sources these from the deployment's kessel. +### Workflows +- Add an optional `alloc` parameter to `default_ci_message` (and + `BuildEnvironment.ci_message`) to override the allocation line. + ## 0.3.0 ### Spack Integration diff --git a/lib/kessel/workflows/__init__.py b/lib/kessel/workflows/__init__.py index e5117ca..31138ad 100644 --- a/lib/kessel/workflows/__init__.py +++ b/lib/kessel/workflows/__init__.py @@ -134,9 +134,9 @@ def default_ci_message(project: str, workflow: str = "default", args: list[str] = sys.argv[1:], pre_alloc_init: str = "", - post_alloc_init: str = "") -> str: + post_alloc_init: str = "", + alloc: str | None = None) -> str: system_change = "" - alloc = "" workflow_change = "" kessel_cmd = "kessel " + " ".join(args) @@ -149,10 +149,16 @@ def default_ci_message(project: str, if post_alloc_init: post_alloc_init += "\n" - if "LLNL_FLUX_SCHEDULER_PARAMETERS" in os.environ: - alloc = f"flux alloc {os.environ['LLNL_FLUX_SCHEDULER_PARAMETERS']}\n" - elif "SCHEDULER_PARAMETERS" in os.environ: - alloc = f"salloc {os.environ['SCHEDULER_PARAMETERS']}\n" + if alloc is None: + if "LLNL_FLUX_SCHEDULER_PARAMETERS" in os.environ: + alloc = f"flux alloc {os.environ['LLNL_FLUX_SCHEDULER_PARAMETERS']}" + elif "SCHEDULER_PARAMETERS" in os.environ: + alloc = f"salloc {os.environ['SCHEDULER_PARAMETERS']}" + else: + alloc = "" + + if alloc: + alloc += "\n" if workflow != "default": workflow_change = f"kessel activate {workflow}\n" diff --git a/lib/kessel/workflows/base/spack/__init__.py b/lib/kessel/workflows/base/spack/__init__.py index bbc1e52..13c44bb 100644 --- a/lib/kessel/workflows/base/spack/__init__.py +++ b/lib/kessel/workflows/base/spack/__init__.py @@ -70,7 +70,8 @@ def init(self) -> None: os.environ["KESSEL_DEPLOYMENT"]).exists()) and "SPACK_ROOT" not in os.environ: raise Exception("No active Spack installation!") - def ci_message(self, parsed_args, pre_alloc_init: str = "", post_alloc_init: str = "") -> str: + def ci_message(self, parsed_args, pre_alloc_init: str = "", post_alloc_init: str = "", + alloc: str | None = None) -> str: if parsed_args.project_spec: project = get_project_name_from_spec(" ".join(parsed_args.project_spec)) else: @@ -81,7 +82,8 @@ def ci_message(self, parsed_args, pre_alloc_init: str = "", post_alloc_init: str system=system, workflow=self.workflow, pre_alloc_init=pre_alloc_init, - post_alloc_init=post_alloc_init) + post_alloc_init=post_alloc_init, + alloc=alloc) def prepare_env(self, args: argparse.Namespace) -> None: self.environ["KESSEL_ENABLE_VIEW"] = "true" if self.view else "false" From 83d9b796d7bf6bdb37da0d1b7ede024656ed0d1c Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Fri, 11 Sep 2026 13:37:32 -0600 Subject: [PATCH 6/9] deployment: add kessel_alloc and interactive node handoff helpers Signed-off-by: Richard Berger --- CHANGELOG.md | 12 ++++-- .../base/spack/deployment/activate.sh | 4 ++ .../base/spack/deployment/alloc-init.sh | 41 +++++++++++++++++++ .../workflows/base/spack/deployment/use.sh | 28 ++++++++++++- 4 files changed, 79 insertions(+), 6 deletions(-) create mode 100755 lib/kessel/workflows/base/spack/deployment/alloc-init.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 2da5038..d39bc33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,14 @@ ### CI/CD - Add `workflows/base/spack/deployment/use.sh` with reusable - `kessel_detect_system` and `kessel_activate_deployment` helpers, so projects no - longer copy the system-detection and clone-or-activate logic into their - `.gitlab/kessel.sh`. The spack-project template now sources these from the - deployment's kessel. + `kessel_detect_system`, `kessel_activate_deployment` and `kessel_alloc` + helpers, so projects no longer copy the system-detection, clone-or-activate and + node-allocation logic into their `.gitlab/kessel.sh`. The spack-project + template now sources these from the deployment's kessel, and the deployment's + `activate.sh` sources them too so they are available in any activated + environment. +- Add `workflows/base/spack/deployment/alloc-init.sh`, the wrapper `kessel_alloc` + hands to the scheduler to activate the deployment on an allocated node. ### Workflows - Add an optional `alloc` parameter to `default_ci_message` (and diff --git a/lib/kessel/workflows/base/spack/deployment/activate.sh b/lib/kessel/workflows/base/spack/deployment/activate.sh index ef225c9..2012fbb 100644 --- a/lib/kessel/workflows/base/spack/deployment/activate.sh +++ b/lib/kessel/workflows/base/spack/deployment/activate.sh @@ -46,3 +46,7 @@ export SPACK_USER_CONFIG_PATH="$KESSEL_DEPLOYMENT/.spack" export SPACK_SYSTEM_CONFIG_PATH="$KESSEL_CONFIG_DIR" source "$KESSEL_DEPLOYMENT/spack/share/spack/setup-env.sh" + +# Make the deployment-use helpers (kessel_detect_system, kessel_alloc, ...) +# available in any activated environment. +source "$KESSEL_ROOT/lib/kessel/workflows/base/spack/deployment/use.sh" diff --git a/lib/kessel/workflows/base/spack/deployment/alloc-init.sh b/lib/kessel/workflows/base/spack/deployment/alloc-init.sh new file mode 100755 index 0000000..a6183f0 --- /dev/null +++ b/lib/kessel/workflows/base/spack/deployment/alloc-init.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# © 2026. Triad National Security, LLC. All rights reserved. +# This program was produced under U.S. Government contract 89233218CNA000001 +# for Los Alamos National Laboratory (LANL), which is operated by Triad +# National Security, LLC for the U.S. Department of Energy/National Nuclear +# Security Administration. All rights in the program are reserved by Triad +# National Security, LLC, and the U.S. Department of Energy/National Nuclear +# Security Administration. The Government is granted for itself and others +# acting on its behalf a nonexclusive, paid-up, irrevocable worldwide license +# in this material to reproduce, prepare derivative works, distribute copies to +# the public, perform publicly and display publicly, and to permit others to do +# so. +# +# Wrapper handed to salloc/flux alloc as $SHELL by kessel_alloc. It runs inside +# the allocation to activate the workflow deployment (creating a writable copy +# only if one doesn't already exist) using KESSEL_DEPLOYMENT inherited from the +# allocating shell, then hands off to a login bash. + +source "$KESSEL_DEPLOYMENT/kessel/lib/kessel/workflows/base/spack/deployment/use.sh" +kessel_activate_deployment || exit $? + +export SHELL=/bin/bash +export -f kessel + +KESSEL_VERSION=$(cat "$KESSEL_WORKFLOW_DEPLOYMENT/.kessel_version" 2>/dev/null) + +BOLD='\033[1m' +RESET='\033[0m' + +echo +echo -e "${BOLD}Spack deployment and Kessel${KESSEL_VERSION:+ ($KESSEL_VERSION)} are now available${RESET}" +echo +echo "Common commands:" +echo +echo -e "${BOLD}spack env list${RESET} show available environments" +echo -e "${BOLD}kessel run${RESET} run workflow with $KESSEL_SPACK_ENV Spack environment" +echo -e "${BOLD}kessel status${RESET} show current workflow state" +echo -e "${BOLD}kessel build-env${RESET} enter build and run environment (requires completed configure step)" +echo + +exec /bin/bash -l diff --git a/lib/kessel/workflows/base/spack/deployment/use.sh b/lib/kessel/workflows/base/spack/deployment/use.sh index ff0e16b..b34abc4 100644 --- a/lib/kessel/workflows/base/spack/deployment/use.sh +++ b/lib/kessel/workflows/base/spack/deployment/use.sh @@ -10,8 +10,8 @@ # the public, perform publicly and display publicly, and to permit others to do # so. # -# Helpers for using an existing Spack deployment from a project's CI: detect the -# current system and activate the (possibly cloned) deployment. +# Helpers for using an existing Spack deployment: detect the current system, +# activate the (possibly cloned) deployment, and allocate an interactive node. # Intended to work on sh, zsh, and bash. # Detect the current cluster (KESSEL_SYSTEM) and its scheduler @@ -56,3 +56,27 @@ kessel_activate_deployment() { unset _KESSEL_WORKFLOW_DEPLOYMENT } + +# Allocate an interactive compute node and drop into the deployment on it. All +# arguments are forwarded to the scheduler as already-split allocation +# parameters. Requires kessel_detect_system to have set KESSEL_SYSTEM_SCHEDULER. +kessel_alloc() { + _kessel_alloc_init="$KESSEL_DEPLOYMENT/kessel/lib/kessel/workflows/base/spack/deployment/alloc-init.sh" + case "$KESSEL_SYSTEM_SCHEDULER" in + slurm) + echo "salloc $*" >&2 + export SHELL="$_kessel_alloc_init" + salloc "$@" + ;; + flux) + echo "flux alloc $* $_kessel_alloc_init" >&2 + flux alloc "$@" "$_kessel_alloc_init" + ;; + *) + echo "ERROR: unknown scheduler '$KESSEL_SYSTEM_SCHEDULER'!" >&2 + unset _kessel_alloc_init + return 1 + ;; + esac + unset _kessel_alloc_init +} From cce04d9dfe38f7ae3f22962c79a6e6912e523265 Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Fri, 11 Sep 2026 14:02:31 -0600 Subject: [PATCH 7/9] add --persist command-line helper for alloc scripts Signed-off-by: Richard Berger --- CHANGELOG.md | 3 ++ .../workflows/base/spack/deployment/use.sh | 42 ++++++++++++++++++- .../templates/spack-project/.gitlab/kessel.sh | 1 + 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d39bc33..9f6043b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ environment. - Add `workflows/base/spack/deployment/alloc-init.sh`, the wrapper `kessel_alloc` hands to the scheduler to activate the deployment on an allocated node. +- Add a `--persist PATH` option (via `kessel_parse_persist`) to the deployment + entry-point scripts, so users can create a reusable writable deployment without + having to know about the `KESSEL_WORKFLOW_DEPLOYMENT` environment variable. ### Workflows - Add an optional `alloc` parameter to `default_ci_message` (and diff --git a/lib/kessel/workflows/base/spack/deployment/use.sh b/lib/kessel/workflows/base/spack/deployment/use.sh index b34abc4..17e750b 100644 --- a/lib/kessel/workflows/base/spack/deployment/use.sh +++ b/lib/kessel/workflows/base/spack/deployment/use.sh @@ -27,10 +27,48 @@ kessel_detect_system() { export KESSEL_SYSTEM KESSEL_SYSTEM_SCHEDULER } +# Parse a "--persist PATH" option out of the arguments, exporting +# KESSEL_WORKFLOW_DEPLOYMENT=PATH so the deployment is created (if needed) at a +# persistent, writable location instead of the default per-user temp copy. When +# --persist is not given, any externally-set KESSEL_WORKFLOW_DEPLOYMENT is left +# untouched. The remaining arguments are placed, shell-quoted, in +# KESSEL_ARGS_REST for the caller to re-apply, e.g.: +# +# kessel_parse_persist "$@" || return $? +# eval "set -- $KESSEL_ARGS_REST" +kessel_parse_persist() { + KESSEL_ARGS_REST="" + while [ "$#" -gt 0 ]; do + case "$1" in + --persist) + if [ "$#" -lt 2 ]; then + echo "ERROR: --persist requires a PATH argument!" >&2 + return 2 + fi + export KESSEL_WORKFLOW_DEPLOYMENT="$2" + shift 2 + ;; + --persist=*) + export KESSEL_WORKFLOW_DEPLOYMENT="${1#--persist=}" + shift + ;; + *) + # Single-quote each remaining arg (escaping embedded quotes) so + # constraint strings survive the eval unscathed. + _kessel_q=$(printf '%s' "$1" | sed "s/'/'\\\\''/g") + KESSEL_ARGS_REST="${KESSEL_ARGS_REST:+$KESSEL_ARGS_REST }'$_kessel_q'" + shift + ;; + esac + done + unset _kessel_q +} + # Activate the writable workflow deployment (KESSEL_WORKFLOW_DEPLOYMENT), # cloning it from the cluster deployment (KESSEL_DEPLOYMENT) if needed. Defaults -# to a per-user temp copy; set KESSEL_WORKFLOW_DEPLOYMENT to a path to persist it, -# or to "upstream" to use the (read-only) cluster deployment directly. +# to a per-user temp copy; pass "--persist PATH" (see kessel_parse_persist) or +# set KESSEL_WORKFLOW_DEPLOYMENT to a path to persist it, or to "upstream" to use +# the (read-only) cluster deployment directly. kessel_activate_deployment() { _KESSEL_WORKFLOW_DEPLOYMENT="$KESSEL_WORKFLOW_DEPLOYMENT" export KESSEL_WORKFLOW_DEPLOYMENT=${KESSEL_WORKFLOW_DEPLOYMENT:-${TMPDIR:-/tmp}/$USER-ci-envs} diff --git a/share/kessel/templates/spack-project/.gitlab/kessel.sh b/share/kessel/templates/spack-project/.gitlab/kessel.sh index ebb3470..d292e62 100644 --- a/share/kessel/templates/spack-project/.gitlab/kessel.sh +++ b/share/kessel/templates/spack-project/.gitlab/kessel.sh @@ -31,5 +31,6 @@ fi source "$_KESSEL_DEPLOYMENT_USE" unset _KESSEL_DEPLOYMENT_USE +kessel_parse_persist "$@" || return $? kessel_detect_system kessel_activate_deployment From fd7b2f49d971e6bd9b460932f92a006a25646dc3 Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Mon, 21 Sep 2026 19:03:10 -0600 Subject: [PATCH 8/9] add helper to list all environments in deployment Signed-off-by: Richard Berger --- lib/kessel/workflows/base/spack/deployment/use.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/kessel/workflows/base/spack/deployment/use.sh b/lib/kessel/workflows/base/spack/deployment/use.sh index 17e750b..4e4e52e 100644 --- a/lib/kessel/workflows/base/spack/deployment/use.sh +++ b/lib/kessel/workflows/base/spack/deployment/use.sh @@ -27,6 +27,19 @@ kessel_detect_system() { export KESSEL_SYSTEM KESSEL_SYSTEM_SCHEDULER } +# List all available Spack environments in the deployment. +kessel_list_environments() { + if [ -n "$KESSEL_DEPLOYMENT" ] && [ -d "$KESSEL_DEPLOYMENT/environments" ]; then + echo "" + echo "Available environments on $KESSEL_SYSTEM:" + find "$KESSEL_DEPLOYMENT/environments" -name "spack.yaml" -type f 2>/dev/null | while read -r yaml_path; do + env_dir=$(dirname "$yaml_path") + rel_path="${env_dir#$KESSEL_DEPLOYMENT/environments/}" + echo " - $rel_path" + done + fi +} + # Parse a "--persist PATH" option out of the arguments, exporting # KESSEL_WORKFLOW_DEPLOYMENT=PATH so the deployment is created (if needed) at a # persistent, writable location instead of the default per-user temp copy. When From 9f5afc0e4c0eb9cf30f17bf6cc4954a7c34cc175 Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Tue, 29 Sep 2026 16:55:42 -0600 Subject: [PATCH 9/9] WIP --- .../base/spack/deployment/__init__.py | 16 ++++ .../workflows/base/spack/deployment/use.sh | 67 ---------------- .../workflows/base/spack/deployment/util.py | 78 +++++++++++++++++++ 3 files changed, 94 insertions(+), 67 deletions(-) create mode 100644 lib/kessel/workflows/base/spack/deployment/__init__.py create mode 100644 lib/kessel/workflows/base/spack/deployment/util.py diff --git a/lib/kessel/workflows/base/spack/deployment/__init__.py b/lib/kessel/workflows/base/spack/deployment/__init__.py new file mode 100644 index 0000000..b4e03ee --- /dev/null +++ b/lib/kessel/workflows/base/spack/deployment/__init__.py @@ -0,0 +1,16 @@ +# © 2026. Triad National Security, LLC. All rights reserved. +# This program was produced under U.S. Government contract 89233218CNA000001 +# for Los Alamos National Laboratory (LANL), which is operated by Triad +# National Security, LLC for the U.S. Department of Energy/National Nuclear +# Security Administration. All rights in the program are reserved by Triad +# National Security, LLC, and the U.S. Department of Energy/National Nuclear +# Security Administration. The Government is granted for itself and others +# acting on its behalf a nonexclusive, paid-up, irrevocable worldwide license +# in this material to reproduce, prepare derivative works, distribute copies to +# the public, perform publicly and display publicly, and to permit others to do +# so. +"""Helpers for using an existing Spack deployment from a project checkout. + +Project scripts like `.gitlab/alloc` import this before kessel or Spack are +activated, so nothing here may depend on an activated environment. +""" diff --git a/lib/kessel/workflows/base/spack/deployment/use.sh b/lib/kessel/workflows/base/spack/deployment/use.sh index 4e4e52e..6abb76a 100644 --- a/lib/kessel/workflows/base/spack/deployment/use.sh +++ b/lib/kessel/workflows/base/spack/deployment/use.sh @@ -10,73 +10,6 @@ # the public, perform publicly and display publicly, and to permit others to do # so. # -# Helpers for using an existing Spack deployment: detect the current system, -# activate the (possibly cloned) deployment, and allocate an interactive node. -# Intended to work on sh, zsh, and bash. - -# Detect the current cluster (KESSEL_SYSTEM) and its scheduler -# (KESSEL_SYSTEM_SCHEDULER). -kessel_detect_system() { - if command -v sacctmgr >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then - KESSEL_SYSTEM=$(sacctmgr list --json clusters | jq -r '.clusters[0].name') - KESSEL_SYSTEM_SCHEDULER="slurm" - elif command -v flux >/dev/null 2>&1; then - KESSEL_SYSTEM=$(hostname | sed 's/[0-9]//g') - KESSEL_SYSTEM_SCHEDULER="flux" - fi - export KESSEL_SYSTEM KESSEL_SYSTEM_SCHEDULER -} - -# List all available Spack environments in the deployment. -kessel_list_environments() { - if [ -n "$KESSEL_DEPLOYMENT" ] && [ -d "$KESSEL_DEPLOYMENT/environments" ]; then - echo "" - echo "Available environments on $KESSEL_SYSTEM:" - find "$KESSEL_DEPLOYMENT/environments" -name "spack.yaml" -type f 2>/dev/null | while read -r yaml_path; do - env_dir=$(dirname "$yaml_path") - rel_path="${env_dir#$KESSEL_DEPLOYMENT/environments/}" - echo " - $rel_path" - done - fi -} - -# Parse a "--persist PATH" option out of the arguments, exporting -# KESSEL_WORKFLOW_DEPLOYMENT=PATH so the deployment is created (if needed) at a -# persistent, writable location instead of the default per-user temp copy. When -# --persist is not given, any externally-set KESSEL_WORKFLOW_DEPLOYMENT is left -# untouched. The remaining arguments are placed, shell-quoted, in -# KESSEL_ARGS_REST for the caller to re-apply, e.g.: -# -# kessel_parse_persist "$@" || return $? -# eval "set -- $KESSEL_ARGS_REST" -kessel_parse_persist() { - KESSEL_ARGS_REST="" - while [ "$#" -gt 0 ]; do - case "$1" in - --persist) - if [ "$#" -lt 2 ]; then - echo "ERROR: --persist requires a PATH argument!" >&2 - return 2 - fi - export KESSEL_WORKFLOW_DEPLOYMENT="$2" - shift 2 - ;; - --persist=*) - export KESSEL_WORKFLOW_DEPLOYMENT="${1#--persist=}" - shift - ;; - *) - # Single-quote each remaining arg (escaping embedded quotes) so - # constraint strings survive the eval unscathed. - _kessel_q=$(printf '%s' "$1" | sed "s/'/'\\\\''/g") - KESSEL_ARGS_REST="${KESSEL_ARGS_REST:+$KESSEL_ARGS_REST }'$_kessel_q'" - shift - ;; - esac - done - unset _kessel_q -} - # Activate the writable workflow deployment (KESSEL_WORKFLOW_DEPLOYMENT), # cloning it from the cluster deployment (KESSEL_DEPLOYMENT) if needed. Defaults # to a per-user temp copy; pass "--persist PATH" (see kessel_parse_persist) or diff --git a/lib/kessel/workflows/base/spack/deployment/util.py b/lib/kessel/workflows/base/spack/deployment/util.py new file mode 100644 index 0000000..8e4c168 --- /dev/null +++ b/lib/kessel/workflows/base/spack/deployment/util.py @@ -0,0 +1,78 @@ +# © 2026. Triad National Security, LLC. All rights reserved. +# This program was produced under U.S. Government contract 89233218CNA000001 +# for Los Alamos National Laboratory (LANL), which is operated by Triad +# National Security, LLC for the U.S. Department of Energy/National Nuclear +# Security Administration. All rights in the program are reserved by Triad +# National Security, LLC, and the U.S. Department of Energy/National Nuclear +# Security Administration. The Government is granted for itself and others +# acting on its behalf a nonexclusive, paid-up, irrevocable worldwide license +# in this material to reproduce, prepare derivative works, distribute copies to +# the public, perform publicly and display publicly, and to permit others to do +# so. + +import argparse +import json +import re +import shutil +import socket +import subprocess + +from pathlib import Path + + +def detect_system_name(): + """Detect the current cluster name""" + if shutil.which("sacctmgr"): + return json.loads(subprocess.check_output(["sacctmgr", "list", "--json", "clusters"]))['clusters'][0]['name'] + if shutil.which("flux"): + return re.sub(r'[0-9]+', "", socket.gethostname()) + return None + + +def detect_batch_system(): + """Detect batch system on a cluster""" + if shutil.which("sacctmgr"): + return "slurm" + if shutil.which("flux"): + return "flux" + return None + + +def deployment_environments(deployment_dir : Path | str) -> List[str]: + """Return all available Spack environment in a given deployment""" + env_dir = Path(deployment_dir) / "environments" + return [str(p.parent.relative_to(env_dir)) for p in env_dir.rglob("spack.yaml") if p.is_file()] + + +def alloc_main(prog_name, exports, params_callback): + parser = argparse.ArgumentParser(prog=prog_name, description=("Allocate a compute node for a given Spack environment via the job scheduler.")) + + parser.add_argument( + "--persist", + metavar="PATH", + help=( + "keep a reusable writable deployment at PATH instead of a " + "temporary per-user copy (sets KESSEL_WORKFLOW_DEPLOYMENT)" + ), + ) + parser.add_argument( + "--print-params", + action="store_true", + help=( + "print the resolved scheduler parameters for SPACK_ENV_NAME and exit " + "without allocating" + ), + ) + parser.add_argument( + "spack_env_name", + metavar="SPACK_ENV_NAME", + help="Spack environment to allocate for", + ) + parser.add_argument( + "scheduler_args", + metavar="extra scheduler args", + nargs=argparse.REMAINDER, + help="additional arguments passed to the scheduler", + ) + args = parser.parse_args() + return 0