From 9e96560f393c37d80f2e9b383d5e04c608bdb747 Mon Sep 17 00:00:00 2001 From: felipecr Date: Tue, 9 Jun 2026 16:48:39 +0200 Subject: [PATCH 1/4] added sarusctl user home config fallback --- crates/sarusctl/src/lib.rs | 167 +++++++++++++++++++++++++++++++++---- 1 file changed, 153 insertions(+), 14 deletions(-) diff --git a/crates/sarusctl/src/lib.rs b/crates/sarusctl/src/lib.rs index a49fe5d..b057644 100644 --- a/crates/sarusctl/src/lib.rs +++ b/crates/sarusctl/src/lib.rs @@ -141,7 +141,7 @@ pub trait UserContext { } pub trait RasterOps { - fn load_config(&self) -> Result; + fn load_config_path(&self, path: &Path) -> Result; fn validate(&self, path: &str) -> Result<(), String>; fn render(&self, path: &str) -> Result; } @@ -191,8 +191,9 @@ pub struct AppDeps<'a> { pub struct RealRasterOps; impl RasterOps for RealRasterOps { - fn load_config(&self) -> Result { - raster::load_config().map_err(|e| AppError::ConfigLoad(e.to_string())) + fn load_config_path(&self, path: &Path) -> Result { + raster::load_config_path(Some(path.to_path_buf()), raster::config::VarExpand::Must, &None) + .map_err(|e| AppError::ConfigLoad(e.to_string())) } fn validate(&self, path: &str) -> Result<(), String> { @@ -206,6 +207,54 @@ impl RasterOps for RealRasterOps { pub struct RealContainerRuntime; +const SYSTEM_CONFIG_DIR: &str = "/etc/sarus-suite"; +const USER_CONFIG_DIR_NAME: &str = "sarus-suite"; + +fn load_config_with_fallback(raster: &dyn RasterOps) -> Result { + let mut candidates = vec![PathBuf::from(SYSTEM_CONFIG_DIR)]; + if let Some(user_dir) = user_config_dir() { + candidates.push(user_dir); + } + + load_config_from_candidates(raster, &candidates) +} + +fn load_config_from_candidates( + raster: &dyn RasterOps, + candidates: &[PathBuf], +) -> Result { + let mut searched = Vec::with_capacity(candidates.len()); + + for path in candidates { + searched.push(path.display().to_string()); + if config_dir_exists(path)? { + return raster.load_config_path(path); + } + } + + Err(AppError::ConfigLoad(format!( + "Cannot find config files in {}", + searched.join(" or ") + ))) +} + +fn config_dir_exists(path: &Path) -> Result { + match fs::metadata(path) { + Ok(_) => Ok(true), + Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(false), + Err(err) => Err(AppError::ConfigLoad(format!( + "Failed to access configuration directory {}: {err}", + path.display() + ))), + } +} + +fn user_config_dir() -> Option { + std::env::var_os("HOME") + .map(PathBuf::from) + .map(|path| path.join(".config").join(USER_CONFIG_DIR_NAME)) +} + impl ContainerRuntime for RealContainerRuntime { fn default_graphroot(&self, ctx: &PodmanCtx) -> Result { let output = pmd::info(Some("{{.Store.GraphRoot}}"), Some(ctx)); @@ -610,12 +659,12 @@ pub fn execute_command_with_options( CommandSpec::Pull { image } => { // pull_command() and migrate_command() are also called internally by other functions, // so they receive the config from the outside to avoid loading it multiple times. - let config = deps.raster.load_config()?; + let config = load_config_with_fallback(deps.raster)?; setup_imagestore(&config)?; pull_command(&image, &config, deps, options) } CommandSpec::Migrate { image } => { - let config = deps.raster.load_config()?; + let config = load_config_with_fallback(deps.raster)?; setup_imagestore(&config)?; migrate_command(&image, &config, deps, options) } @@ -644,7 +693,7 @@ fn render_command(filepath: &str, deps: &AppDeps<'_>) -> Result) -> Result { - let config = deps.raster.load_config()?; + let config = load_config_with_fallback(deps.raster)?; let seed_ctx = build_parallax_seed_ctx(&config); // We need to find and explicitly state the graphroot because it needs to be passed to Parallax under the hood. @@ -715,7 +764,7 @@ fn rmi_command( deps: &AppDeps<'_>, options: ExecOptions, ) -> Result { - let config = deps.raster.load_config()?; + let config = load_config_with_fallback(deps.raster)?; let seed_ctx = build_parallax_seed_ctx(&config); // We need to find and explicitly state the graphroot because it needs to be passed to Parallax under the hood. @@ -743,7 +792,7 @@ fn run_command( Ok(edf) => { // Loading config in each branch is a small duplication, // but allows to integration test invalid EDF cases without needing a valid config present - let config = deps.raster.load_config()?; + let config = load_config_with_fallback(deps.raster)?; setup_imagestore(&config)?; run_edf_command(&edf, container_cmd, &config, deps, options) } @@ -754,7 +803,7 @@ fn run_command( AppError::UnsupportedInput(format!("{filepath} is not valid EDF nor YAML")) })?; - let config = deps.raster.load_config()?; + let config = load_config_with_fallback(deps.raster)?; setup_imagestore(&config)?; run_yaml_command(filepath, container_cmd, &config, deps, options) } @@ -1006,8 +1055,11 @@ mod tests { fn sample_config() -> Config { Config { - parallax_imagestore: String::from("/scratch/user/parallax/store"), + parallax_imagestore: String::from("/tmp/sarusctl-tests/parallax/store"), parallax_mount_program: String::from("/usr/local/bin/parallax_mount_program"), + parallax_mp_uid: 1234, + parallax_mp_gid: 4321, + parallax_mp_logfile: String::from("/tmp/parallax-1234/mount_program.log"), parallax_path: String::from("/usr/local/bin/parallax"), podman_module: String::from("hpc"), podman_path: String::from("/usr/bin/podman"), @@ -1030,22 +1082,36 @@ mod tests { struct FakeRasterOps { config: Result, + config_paths: RefCell>, validate_results: HashMap>, render_results: HashMap>, } impl FakeRasterOps { fn new(config: Config) -> Self { + ensure_default_user_config_dir(); Self { config: Ok(config), + config_paths: RefCell::new(Vec::new()), validate_results: HashMap::new(), render_results: HashMap::new(), } } + + fn config_paths(&self) -> Vec { + self.config_paths.borrow().clone() + } + } + + fn ensure_default_user_config_dir() { + if let Some(path) = user_config_dir() { + fs::create_dir_all(path).unwrap(); + } } impl RasterOps for FakeRasterOps { - fn load_config(&self) -> Result { + fn load_config_path(&self, path: &Path) -> Result { + self.config_paths.borrow_mut().push(path.to_path_buf()); self.config.clone() } @@ -1245,6 +1311,79 @@ mod tests { } } + #[test] + fn load_config_from_candidates_uses_first_existing_directory() { + let temp = tempdir().unwrap(); + let system_dir = temp.path().join("system"); + let user_dir = temp.path().join("user"); + fs::create_dir_all(&system_dir).unwrap(); + fs::create_dir_all(&user_dir).unwrap(); + + let raster = FakeRasterOps::new(sample_config()); + let config = load_config_from_candidates(&raster, &[system_dir.clone(), user_dir]).unwrap(); + + assert_eq!(config.parallax_path, sample_config().parallax_path); + assert_eq!(raster.config_paths(), vec![system_dir]); + } + + #[test] + fn load_config_from_candidates_falls_back_when_system_directory_is_missing() { + let temp = tempdir().unwrap(); + let system_dir = temp.path().join("system"); + let user_dir = temp.path().join("user"); + fs::create_dir_all(&user_dir).unwrap(); + + let raster = FakeRasterOps::new(sample_config()); + let config = + load_config_from_candidates(&raster, &[system_dir, user_dir.clone()]).unwrap(); + + assert_eq!(config.parallax_path, sample_config().parallax_path); + assert_eq!(raster.config_paths(), vec![user_dir]); + } + + #[test] + fn load_config_from_candidates_does_not_fallback_after_existing_directory_errors() { + let temp = tempdir().unwrap(); + let system_dir = temp.path().join("system"); + let user_dir = temp.path().join("user"); + fs::create_dir_all(&system_dir).unwrap(); + fs::create_dir_all(&user_dir).unwrap(); + + let mut raster = FakeRasterOps::new(sample_config()); + raster.config = Err(AppError::ConfigLoad(String::from("invalid config"))); + + let err = match load_config_from_candidates(&raster, &[system_dir.clone(), user_dir]) { + Ok(_) => panic!("expected config load to fail"), + Err(err) => err, + }; + + assert_eq!(err, AppError::ConfigLoad(String::from("invalid config"))); + assert_eq!(raster.config_paths(), vec![system_dir]); + } + + #[test] + fn load_config_from_candidates_reports_both_missing_locations() { + let temp = tempdir().unwrap(); + let system_dir = temp.path().join("system"); + let user_dir = temp.path().join("user"); + let raster = FakeRasterOps::new(sample_config()); + + let err = match load_config_from_candidates(&raster, &[system_dir.clone(), user_dir.clone()]) { + Ok(_) => panic!("expected config load to fail"), + Err(err) => err, + }; + + assert_eq!( + err, + AppError::ConfigLoad(format!( + "Cannot find config files in {} or {}", + system_dir.display(), + user_dir.display() + )) + ); + assert!(raster.config_paths().is_empty()); + } + fn unique_test_user() -> CurrentUser { CurrentUser { uid: Uuid::new_v4().as_u128() as u32, @@ -1525,14 +1664,14 @@ spec: assert_eq!(seed.podman_path, PathBuf::from("/usr/bin/podman")); assert_eq!( seed.ro_store, - Some(PathBuf::from("/scratch/user/parallax/store")) + Some(PathBuf::from("/tmp/sarusctl-tests/parallax/store")) ); let ro = build_readonly_ctx(&config); assert_eq!(ro.podman_path, PathBuf::from("/usr/bin/podman")); assert_eq!( ro.graphroot, - Some(PathBuf::from("/scratch/user/parallax/store")) + Some(PathBuf::from("/tmp/sarusctl-tests/parallax/store")) ); let run = build_run_ctx(&config, &user); @@ -1548,7 +1687,7 @@ spec: ); assert_eq!( seed.ro_store, - Some(PathBuf::from("/scratch/user/parallax/store")) + Some(PathBuf::from("/tmp/sarusctl-tests/parallax/store")) ); let env = run.podman_env.expect("missing env"); assert_eq!(env.get(OsStr::new("PARALLAX_MP_UID")).unwrap(), "1234"); From eb498a1657d8ed8634b1a367c02da6e585ddda2f Mon Sep 17 00:00:00 2001 From: felipecr Date: Sun, 14 Jun 2026 13:46:20 +0200 Subject: [PATCH 2/4] unpin arch in devcontainer to use the host one --- .devcontainer/opensuse/Dockerfile | 3 +-- .devcontainer/opensuse/devcontainer.json | 5 ----- .devcontainer/ubuntu/Dockerfile | 2 +- .devcontainer/ubuntu/devcontainer.json | 3 --- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.devcontainer/opensuse/Dockerfile b/.devcontainer/opensuse/Dockerfile index 1bfc97e..b9ad06c 100644 --- a/.devcontainer/opensuse/Dockerfile +++ b/.devcontainer/opensuse/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=linux/amd64 opensuse/leap:15.5 +FROM opensuse/leap:15.5 # Basic tooling + deps RUN zypper -n refresh && \ @@ -72,4 +72,3 @@ USER root ENV CPATH=/usr/local/include:/usr/local/include/slurm:/usr/include - diff --git a/.devcontainer/opensuse/devcontainer.json b/.devcontainer/opensuse/devcontainer.json index 1ddfb50..e1ae308 100644 --- a/.devcontainer/opensuse/devcontainer.json +++ b/.devcontainer/opensuse/devcontainer.json @@ -6,10 +6,6 @@ "remoteUser": "vscode", "updateRemoteUserUID": true, - "platform": "linux/amd64", - - "runArgs": ["--platform=linux/amd64"], - "mounts": [ "type=volume,source=skybox-target,target=/workspaces/target" ], @@ -33,4 +29,3 @@ } } } - diff --git a/.devcontainer/ubuntu/Dockerfile b/.devcontainer/ubuntu/Dockerfile index 4ff9111..ebe426b 100644 --- a/.devcontainer/ubuntu/Dockerfile +++ b/.devcontainer/ubuntu/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=linux/amd64 ghcr.io/sarus-suite/cluster-tooling/skybox-dev-base:latest AS base +FROM ghcr.io/sarus-suite/cluster-tooling/skybox-dev-base:latest AS base FROM base AS chef-planner diff --git a/.devcontainer/ubuntu/devcontainer.json b/.devcontainer/ubuntu/devcontainer.json index 2dc655c..0a15786 100644 --- a/.devcontainer/ubuntu/devcontainer.json +++ b/.devcontainer/ubuntu/devcontainer.json @@ -8,9 +8,6 @@ "remoteUser": "vscode", "updateRemoteUserUID": true, - "platform": "linux/amd64", - "runArgs": ["--platform=linux/amd64"], - "customizations": { "vscode": { "extensions": [ From c838bda2ebd015b5d46202e0842fa3ff5dda2eac Mon Sep 17 00:00:00 2001 From: felipecr Date: Sun, 14 Jun 2026 14:25:45 +0200 Subject: [PATCH 3/4] added alpine devcontainer for sarusctl static builds --- .devcontainer/alpine/Containerfile | 47 ++++++++++++++++++++++++++ .devcontainer/alpine/devcontainer.json | 41 ++++++++++++++++++++++ README.md | 13 ++++++- 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/alpine/Containerfile create mode 100644 .devcontainer/alpine/devcontainer.json diff --git a/.devcontainer/alpine/Containerfile b/.devcontainer/alpine/Containerfile new file mode 100644 index 0000000..438fb7d --- /dev/null +++ b/.devcontainer/alpine/Containerfile @@ -0,0 +1,47 @@ +FROM alpine:3.22 + +ARG USERNAME=vscode +ARG USER_UID=1000 +ARG USER_GID=1000 + +RUN apk add --no-cache \ + bash \ + build-base \ + ca-certificates \ + curl \ + git \ + musl-dev \ + openssl-dev \ + openssl-libs-static \ + pkgconf \ + sudo \ + zlib-dev \ + zlib-static + +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:${PATH} + +RUN addgroup -g ${USER_GID} ${USERNAME} && \ + adduser -D -u ${USER_UID} -G ${USERNAME} -h /home/${USERNAME} -s /bin/bash ${USERNAME} && \ + echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/99-${USERNAME} && \ + chmod 0440 /etc/sudoers.d/99-${USERNAME} && \ + mkdir -p /home/${USERNAME} /usr/local/cargo /usr/local/rustup /workspaces/cluster-tooling && \ + chown -R ${USER_UID}:${USER_GID} /home/${USERNAME} /usr/local/cargo /usr/local/rustup /workspaces/cluster-tooling + +ENV HOME=/home/${USERNAME} + +USER ${USERNAME} +WORKDIR /workspaces/cluster-tooling + +RUN curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable && \ + rustup component add rustfmt clippy rust-analyzer && \ + rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl && \ + rustc -V && cargo -V + +ENV CARGO_PROFILE_RELEASE_LTO=fat \ + CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 \ + CARGO_PROFILE_RELEASE_STRIP=true \ + PKG_CONFIG_ALL_STATIC=1 \ + OPENSSL_STATIC=1 \ + LIBZ_SYS_STATIC=1 diff --git a/.devcontainer/alpine/devcontainer.json b/.devcontainer/alpine/devcontainer.json new file mode 100644 index 0000000..afd5728 --- /dev/null +++ b/.devcontainer/alpine/devcontainer.json @@ -0,0 +1,41 @@ +{ + "name": "sarusctl-static (alpine + musl)", + "build": { + "dockerfile": "Containerfile", + "context": "../..", + "args": { + "USERNAME": "${localEnv:USER:vscode}", + "USER_UID": "${localEnv:UID:1000}", + "USER_GID": "${localEnv:GID:1000}" + } + }, + "workspaceFolder": "/workspaces/cluster-tooling", + "containerEnv": { + "HOME": "/home/${localEnv:USER:vscode}" + }, + "remoteUser": "${localEnv:USER:vscode}", + "updateRemoteUserUID": false, + "runArgs": ["--userns=host"], + "mounts": [ + "type=volume,source=sarusctl-static-cargo-registry,target=/usr/local/cargo/registry", + "type=volume,source=sarusctl-static-cargo-git,target=/usr/local/cargo/git", + "type=volume,source=sarusctl-static-rustup,target=/usr/local/rustup" + ], + "postCreateCommand": "sh -lc 'sudo mkdir -p /usr/local/cargo/registry /usr/local/cargo/git /usr/local/rustup && sudo chown -R $(id -u):$(id -g) /usr/local/cargo /usr/local/rustup'", + "customizations": { + "vscode": { + "extensions": [ + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + "fill-labs.dependi", + "vadimcn.vscode-lldb", + "openai.chatgpt", + "anthropic.claude-code" + ], + "settings": { + "rust-analyzer.check.command": "clippy", + "editor.formatOnSave": true + } + } + } +} diff --git a/README.md b/README.md index ea2b21a..da71948 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,18 @@ devcontainer exec --workspace-folder . --config .devcontainer/opensuse/devcontai You are now inside the prepared build environment. +### Static `sarusctl` build with Alpine + +For a static `musl` build of `sarusctl`, use the Alpine devcontainer instead: + +```bash +devcontainer up --workspace-folder . --config .devcontainer/alpine/devcontainer.json +devcontainer exec --workspace-folder . --config .devcontainer/alpine/devcontainer.json -- \ + cargo build --locked -p sarusctl --release +``` + +This devcontainer is intentionally scoped to `sarusctl` portability work. It does not include the Slurm headers and related system setup needed for `skybox`. + ### VS Code Open the repository in VS Code and choose: @@ -252,4 +264,3 @@ Then work on a new feature See LICENSE file for this repository. See individual crates for their licensing terms. - From 5ced1a27b2e66bc7bd98a069cbe2e7b266b21744 Mon Sep 17 00:00:00 2001 From: felipecr Date: Tue, 16 Jun 2026 01:59:43 +0200 Subject: [PATCH 4/4] sarusctl, implementing cleanup via podman system reset of tmp dirs --- crates/podman-driver/src/lib.rs | 26 +++++++++++++++++++ crates/sarusctl/src/lib.rs | 44 +++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/crates/podman-driver/src/lib.rs b/crates/podman-driver/src/lib.rs index 47bde0d..4564ddf 100644 --- a/crates/podman-driver/src/lib.rs +++ b/crates/podman-driver/src/lib.rs @@ -281,6 +281,17 @@ mod commands { cmd } + pub fn system_reset(podman_ctx: Option<&PodmanCtx>) -> Command { + let mut cmd = commands::base(podman_ctx); + + if let Some(ctx) = podman_ctx { + cli_opt(&mut cmd, "--module", ctx.module.as_deref().map(OsStr::new)); + } + + cmd.args(["system", "reset", "--force"]); + cmd + } + pub fn kube(podman_ctx: Option<&PodmanCtx>) -> Command { let mut cmd = commands::base(podman_ctx); @@ -485,6 +496,12 @@ pub fn info(format: Option<&str>, podman_ctx: Option<&PodmanCtx>) -> Output { .expect("Failed to execute command") } +pub fn system_reset(podman_ctx: Option<&PodmanCtx>) -> Output { + commands::system_reset(podman_ctx) + .output() + .expect("Failed to execute command") +} + pub fn kube_play(filepath: &str, podman_ctx: Option<&PodmanCtx>) { commands::kube_play(filepath, podman_ctx) .status() @@ -742,6 +759,15 @@ pub mod loggable { } } + pub fn system_reset(podman_ctx: Option<&PodmanCtx>) -> ExecutedCommand { + let mut cmd = commands::system_reset(podman_ctx); + + ExecutedCommand { + command: cmd2string(&cmd), + output: cmd.output().expect("Failed to execute command"), + } + } + fn parallax_execute_command( parallax_path: &PathBuf, podman_ctx: &PodmanCtx, diff --git a/crates/sarusctl/src/lib.rs b/crates/sarusctl/src/lib.rs index b057644..f4f743e 100644 --- a/crates/sarusctl/src/lib.rs +++ b/crates/sarusctl/src/lib.rs @@ -180,6 +180,7 @@ pub trait ContainerRuntime { ) -> Result; fn kube_play(&self, filepath: &str, run_ctx: &PodmanCtx) -> Result<(), AppError>; fn kube_down(&self, filepath: &str, force: bool, run_ctx: &PodmanCtx) -> Result<(), AppError>; + fn cleanup_storage(&self, run_ctx: &PodmanCtx) -> Result<(), AppError>; } pub struct AppDeps<'a> { @@ -373,6 +374,18 @@ impl ContainerRuntime for RealContainerRuntime { pmd::kube_down(filepath, force, Some(run_ctx)); Ok(()) } + + fn cleanup_storage(&self, run_ctx: &PodmanCtx) -> Result<(), AppError> { + let out = pmd::loggable::system_reset(Some(run_ctx)); + if out.output.status.success() { + Ok(()) + } else { + Err(AppError::Runtime(format!( + "Podman storage cleanup failed: {}", + String::from_utf8_lossy(&out.output.stderr).trim() + ))) + } + } } pub struct RealUserContext; @@ -841,7 +854,9 @@ fn run_edf_command( let run_result = deps .runtime .run_from_edf(edf, &run_ctx, &c_ctx, container_cmd); + let storage_cleanup_error = deps.runtime.cleanup_storage(&run_ctx).err(); let cleanup_warning = cleanup_podman_rootdirs(&run_ctx); + let cleanup_warning = merge_cleanup_warning(storage_cleanup_error, cleanup_warning); // Append warning to error in case of run failure output.return_code = match run_result { @@ -894,7 +909,9 @@ fn run_yaml_command( .exec_interactive(&join_container, &run_ctx, container_cmd); let down_result = deps.runtime.kube_down(filepath, true, &run_ctx); // TODO check if we need to do anything else to report errors from kube_down as well + let storage_cleanup_error = deps.runtime.cleanup_storage(&run_ctx).err(); let cleanup_warning = cleanup_podman_rootdirs(&run_ctx); + let cleanup_warning = merge_cleanup_warning(storage_cleanup_error, cleanup_warning); // Append warning to error in case of run failure if let Err(err) = play_result { @@ -960,6 +977,17 @@ fn combine_error_with_warning(err: AppError, warning: String) -> AppError { AppError::Runtime(format!("{err}\n{warning}")) } +fn merge_cleanup_warning( + storage_cleanup_error: Option, + rootdir_cleanup_warning: Option, +) -> Option { + match (storage_cleanup_error, rootdir_cleanup_warning) { + (Some(err), Some(warning)) => Some(format!("{err}\n{warning}")), + (None, warning) => warning, + (Some(_), None) => None, + } +} + fn setup_imagestore(config: &Config) -> Result<(), AppError> { let imagestore = &config.parallax_imagestore; let imagestore_pb = PathBuf::from(&imagestore); @@ -1150,6 +1178,7 @@ mod tests { run_result: Result, kube_play_result: Result<(), AppError>, kube_down_result: Result<(), AppError>, + cleanup_storage_result: Result<(), AppError>, } impl FakeContainerRuntime { @@ -1167,6 +1196,7 @@ mod tests { run_result: Ok(0), kube_play_result: Ok(()), kube_down_result: Ok(()), + cleanup_storage_result: Ok(()), } } @@ -1297,6 +1327,13 @@ mod tests { .push(format!("kube_down:{filepath}?force={force}")); self.kube_down_result.clone() } + + fn cleanup_storage(&self, _run_ctx: &PodmanCtx) -> Result<(), AppError> { + self.calls + .borrow_mut() + .push(String::from("cleanup_storage")); + self.cleanup_storage_result.clone() + } } fn mock_deps<'a>( @@ -1958,7 +1995,8 @@ spec: runtime.calls(), vec![ String::from("image_exists:alpine:3.22"), - String::from("run:alpine:3.22:[\"sh\"]") + String::from("run:alpine:3.22:[\"sh\"]"), + String::from("cleanup_storage"), ] ); } @@ -1996,7 +2034,8 @@ spec: String::from("default_graphroot"), String::from("migrate:alpine:3.22"), String::from("image_exists:alpine:3.22"), - String::from("run:alpine:3.22:[\"sh\"]") + String::from("run:alpine:3.22:[\"sh\"]"), + String::from("cleanup_storage"), ] ); } @@ -2131,6 +2170,7 @@ spec: format!("kube_play:{}", manifest.to_string_lossy()), String::from("exec_interactive:training-pod-sidecar:[]"), format!("kube_down:{}?force=true", manifest.to_string_lossy()), + String::from("cleanup_storage"), ] ); }