kata-deploy: install kata-ctl on the node when debug is enabled

kata-ctl provides `kata-ctl exec <sandbox-id>`, 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 <ffidencio@nvidia.com>
Assisted-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Fabiano Fidêncio
2026-07-22 21:05:27 +02:00
parent 41a203d679
commit 2bf7a53622
2 changed files with 185 additions and 0 deletions

View File

@@ -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

View File

@@ -107,6 +107,9 @@ pub async fn install_artifacts(config: &Config, container_runtime: &str) -> Resu
let mut extracted: HashSet<String> = 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<String>)
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<String>) -> 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<String>,
) -> 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<String> = 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<String> = 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<String> = ["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());
}
}