From 899005859c8a7c580232fbc6561be5c3d22d4d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabiano=20Fid=C3=AAncio?= Date: Mon, 16 Feb 2026 16:59:02 +0100 Subject: [PATCH] kata-deploy: avoid leading/blank lines in written TOML config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When writing containerd drop-in or other TOML (e.g. initially empty file), the serialized document could start with many newlines. Signed-off-by: Fabiano FidĂȘncio --- .../binary/src/runtime/containerd.rs | 27 ++++++++++++ .../kata-deploy/binary/src/utils/toml.rs | 41 ++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/tools/packaging/kata-deploy/binary/src/runtime/containerd.rs b/tools/packaging/kata-deploy/binary/src/runtime/containerd.rs index faef60c003..520391c8ee 100644 --- a/tools/packaging/kata-deploy/binary/src/runtime/containerd.rs +++ b/tools/packaging/kata-deploy/binary/src/runtime/containerd.rs @@ -636,6 +636,33 @@ mod tests { } } + /// Written containerd config (e.g. drop-in) must not start with blank lines when written to an initially empty file. + #[rstest] + #[case(CONTAINERD_V3_RUNTIME_PLUGIN_ID)] + #[case(CONTAINERD_V2_CRI_PLUGIN_ID)] + fn test_write_containerd_runtime_config_empty_file_no_leading_newlines( + #[case] pluginid: &str, + ) { + let file = NamedTempFile::new().unwrap(); + let path = file.path(); + std::fs::write(path, "").unwrap(); + + let params = make_params("kata-qemu", Some("\"nydus\"")); + write_containerd_runtime_config(path, pluginid, ¶ms).unwrap(); + + let content = std::fs::read_to_string(path).unwrap(); + assert!( + !content.starts_with('\n'), + "containerd config must not start with newline(s), got {} leading newlines (pluginid={})", + content.chars().take_while(|&c| c == '\n').count(), + pluginid + ); + assert!( + content.trim_start().starts_with('['), + "config should start with a TOML table" + ); + } + #[rstest] #[case("containerd://1.6.28", true, false, Some("kata-deploy only supports snapshotter configuration with containerd 1.7 or newer"))] #[case("containerd://1.6.28", false, true, None)] diff --git a/tools/packaging/kata-deploy/binary/src/utils/toml.rs b/tools/packaging/kata-deploy/binary/src/utils/toml.rs index 3685d61a87..95c6cd47ca 100644 --- a/tools/packaging/kata-deploy/binary/src/utils/toml.rs +++ b/tools/packaging/kata-deploy/binary/src/utils/toml.rs @@ -65,17 +65,23 @@ fn split_non_toml_header(content: &str) -> (&str, &str) { /// Write a TOML file with an optional non-TOML header (e.g. K3s template line). /// Ensures the header ends with a newline before the TOML body. +/// Trims leading newlines from the serialized document to avoid many blank lines +/// when the file was initially empty (e.g. containerd drop-in). fn write_toml_with_header( file_path: &Path, header: &str, doc: &DocumentMut, ) -> Result<()> { - let normalized_header = if header.ends_with('\n') { + let normalized_header = if header.is_empty() { + String::new() + } else if header.ends_with('\n') { header.to_string() } else { format!("{header}\n") }; - std::fs::write(file_path, format!("{}{}", normalized_header, doc.to_string())) + let body = doc.to_string(); + let body_trimmed = body.trim_start_matches('\n'); + std::fs::write(file_path, format!("{}{}", normalized_header, body_trimmed)) .with_context(|| format!("Failed to write TOML file: {file_path:?}"))?; Ok(()) } @@ -609,6 +615,37 @@ mod tests { assert!(content.contains("runtime_type")); } + #[rstest] + #[case("", "")] + #[case("{{ template \"base\" . }}\n", "{{ template \"base\" . }}\n")] + fn test_set_toml_value_empty_file_no_leading_newlines( + #[case] initial_content: &str, + #[case] expected_prefix: &str, + ) { + let file = NamedTempFile::new().unwrap(); + let path = file.path(); + std::fs::write(path, initial_content).unwrap(); + + set_toml_value( + path, + ".plugins.\"io.containerd.cri.v1.runtime\".containerd.runtimes.kata-qemu.runtime_type", + "\"io.containerd.kata-qemu.v2\"", + ) + .unwrap(); + let content = std::fs::read_to_string(path).unwrap(); + assert!(content.starts_with(expected_prefix), "header/prefix must be preserved"); + let body_start = content.strip_prefix(expected_prefix).unwrap(); + assert!( + !body_start.starts_with('\n'), + "written TOML body must not start with newline(s) after header, got {} leading newlines", + body_start.chars().take_while(|&c| c == '\n').count() + ); + assert!( + body_start.trim_start().starts_with('['), + "body should start with a TOML table" + ); + } + #[test] fn test_get_toml_value() { let file = NamedTempFile::new().unwrap();