kata-deploy: avoid leading/blank lines in written TOML config

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 <ffidencio@nvidia.com>
This commit is contained in:
Fabiano Fidêncio
2026-02-16 16:59:02 +01:00
parent cfa8188cad
commit 899005859c
2 changed files with 66 additions and 2 deletions

View File

@@ -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, &params).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)]

View File

@@ -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();