From 2bf7a53622ce2ebec149fa859f13cc7956d83c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabiano=20Fid=C3=AAncio?= Date: Wed, 22 Jul 2026 21:05:27 +0200 Subject: [PATCH] kata-deploy: install kata-ctl on the node when debug is enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kata-ctl provides `kata-ctl exec `, the runtime-rs entry point into a sandbox's agent debug console. It ships in the kata-tools bundle rather than with the shim binaries, so it is not staged into the kata-deploy image and never lands on the node - leaving runtime-rs deployments without a first-class way to reach the debug console. Signed-off-by: Fabiano FidĂȘncio Assisted-by: Cursor --- .github/workflows/publish-kata-images.yaml | 10 + .../binary/src/artifacts/install.rs | 175 ++++++++++++++++++ 2 files changed, 185 insertions(+) diff --git a/.github/workflows/publish-kata-images.yaml b/.github/workflows/publish-kata-images.yaml index 357e6e9883..4ea313e300 100644 --- a/.github/workflows/publish-kata-images.yaml +++ b/.github/workflows/publish-kata-images.yaml @@ -119,6 +119,16 @@ jobs: path: tools/packaging/kata-deploy/local-build/build merge-multiple: true + # kata-ctl ships in the kata-tools bundle under its own artifact name, so + # stage it into the build dir separately to bake it into the deploy image. + - name: get-kata-ctl for ${{ inputs.arch }} + if: ${{ inputs.artifact-name == '' && matrix.image-kind == 'deploy' }} + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + pattern: kata-tools-artifacts-${{ inputs.arch }}-kata-ctl${{ inputs.tarball-suffix }} + path: tools/packaging/kata-deploy/local-build/build + merge-multiple: true + - name: get-kata-static-tarball for ${{ inputs.arch }} if: ${{ inputs.artifact-name == '' && matrix.image-kind == 'monitor' }} uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 diff --git a/tools/packaging/kata-deploy/binary/src/artifacts/install.rs b/tools/packaging/kata-deploy/binary/src/artifacts/install.rs index 920529cd45..5ded144968 100644 --- a/tools/packaging/kata-deploy/binary/src/artifacts/install.rs +++ b/tools/packaging/kata-deploy/binary/src/artifacts/install.rs @@ -107,6 +107,9 @@ pub async fn install_artifacts(config: &Config, container_runtime: &str) -> Resu let mut extracted: HashSet = HashSet::new(); extract_component_tarballs(config, &mut extracted)?; + // Not tied to any shim, so reconcile separately: turning debug off on a + // redeploy must also remove it. + reconcile_debug_tools(config, &mut extracted)?; install_versions_yaml(config)?; set_executable_permissions(&config.host_install_dir)?; @@ -741,6 +744,106 @@ fn extract_component_tarballs(config: &Config, extracted: &mut HashSet) Ok(()) } +/// Debug-only tools pulled in by the debug flag rather than per-shim membership: +/// kata-ctl ships in the kata-tools bundle, not with the shims. +const DEBUG_TOOL_COMPONENTS: &[&str] = &["kata-ctl"]; + +/// Static list of a tool's installed files, needed to remove it when debug is +/// off: nothing is extracted then, so paths can't be discovered dynamically. +fn debug_tool_paths(component: &str) -> &'static [&'static str] { + match component { + "kata-ctl" => &["bin/kata-ctl"], + _ => &[], + } +} + +/// Extract debug tooling when debug is on, remove it when off so a redeploy +/// never leaves it behind. Extraction is best-effort: a missing tarball only +/// warns, since an image may be built without the tools. +fn reconcile_debug_tools(config: &Config, extracted: &mut HashSet) -> Result<()> { + if config.debug { + extract_debug_tools_in( + DEBUG_TOOL_COMPONENTS, + Path::new(TARBALLS_DIR), + &config.host_install_dir, + true, + extracted, + ) + } else { + remove_debug_tools_in(DEBUG_TOOL_COMPONENTS, Path::new(&config.host_install_dir)); + Ok(()) + } +} + +/// Remove the given debug tools' files. Best-effort and idempotent: only +/// existing files are touched and a failed removal warns instead of aborting. +fn remove_debug_tools_in(components: &[&str], install_dir: &Path) { + for component in components { + for rel in debug_tool_paths(component) { + let path = install_dir.join(rel); + if path.exists() { + info!( + "debug disabled: removing debug tool '{}' ({})", + component, + path.display() + ); + if let Err(e) = fs::remove_file(&path) { + log::warn!("debug: failed to remove {}: {}", path.display(), e); + } + } + } + } +} + +/// Testable core of the debug-tool extraction: takes explicit paths so it can +/// be unit tested without the container image layout. +fn extract_debug_tools_in( + components: &[&str], + tarballs_dir: &Path, + host_install_dir: &str, + debug: bool, + extracted: &mut HashSet, +) -> Result<()> { + if !debug { + return Ok(()); + } + + for component in components { + if extracted.contains(*component) { + continue; + } + + let tarball_name = format!("kata-static-{}.tar.zst", component); + let tarball_path = tarballs_dir.join(&tarball_name); + + if !tarball_path.exists() { + log::warn!( + "debug: optional tool '{}' not found at {}; skipping. Rebuild the \ + kata-deploy image with the '{}' component to enable it.", + component, + tarball_path.display(), + component + ); + continue; + } + + info!("debug: extracting optional tool '{}'", component); + if let Err(e) = extract_tarball(&tarball_path, host_install_dir) { + log::warn!( + "debug: failed to extract optional tool '{}' from {}: {:#}; skipping", + component, + tarball_path.display(), + e + ); + continue; + } + + extracted.insert((*component).to_string()); + } + + Ok(()) +} + fn set_executable_permissions(dir: &str) -> Result<()> { let bin_paths = ["bin", "runtime-rs/bin"]; @@ -1656,4 +1759,76 @@ mod tests { fs::remove_dir_all(&runtime_dir).unwrap(); assert!(!runtime_dir.exists()); } + + #[test] + fn test_extract_debug_tools_disabled_is_noop() { + let tmp = tempfile::tempdir().unwrap(); + let dest = tmp.path().to_str().unwrap(); + let mut extracted: HashSet = HashSet::new(); + + extract_debug_tools_in(&["kata-ctl"], tmp.path(), dest, false, &mut extracted).unwrap(); + + assert!(extracted.is_empty()); + } + + #[test] + fn test_extract_debug_tools_missing_tarball_is_best_effort() { + let tarballs = tempfile::tempdir().unwrap(); + let dest = tempfile::tempdir().unwrap(); + let mut extracted: HashSet = HashSet::new(); + + extract_debug_tools_in( + &["kata-ctl"], + tarballs.path(), + dest.path().to_str().unwrap(), + true, + &mut extracted, + ) + .unwrap(); + + assert!(extracted.is_empty()); + } + + #[test] + fn test_extract_debug_tools_skips_already_extracted() { + let tarballs = tempfile::tempdir().unwrap(); + let dest = tempfile::tempdir().unwrap(); + let mut extracted: HashSet = ["kata-ctl".to_string()].into_iter().collect(); + + extract_debug_tools_in( + &["kata-ctl"], + tarballs.path(), + dest.path().to_str().unwrap(), + true, + &mut extracted, + ) + .unwrap(); + + assert_eq!(extracted.len(), 1); + assert!(extracted.contains("kata-ctl")); + } + + #[test] + fn test_remove_debug_tools_removes_installed_files() { + let install = tempfile::tempdir().unwrap(); + let bin = install.path().join("bin"); + fs::create_dir_all(&bin).unwrap(); + let kata_ctl = bin.join("kata-ctl"); + fs::write(&kata_ctl, "binary").unwrap(); + // A sibling that must be left untouched. + let shim = bin.join("containerd-shim-kata-v2"); + fs::write(&shim, "shim").unwrap(); + + remove_debug_tools_in(&["kata-ctl"], install.path()); + + assert!(!kata_ctl.exists(), "kata-ctl removed when debug is off"); + assert!(shim.exists(), "unrelated binaries untouched"); + } + + #[test] + fn test_remove_debug_tools_is_noop_when_absent() { + let install = tempfile::tempdir().unwrap(); + remove_debug_tools_in(&["kata-ctl"], install.path()); + assert!(!install.path().join("bin/kata-ctl").exists()); + } }