fix(orchestrator): normalize YAML-folded newlines in command drift comparison
Second false-drift source found live on .228 right after the entrypoint split fix: a YAML `>-` script with more-indented continuation lines keeps literal newlines in custom_args, but shell_join flattens them to spaces when writing the quadlet Exec= line, so Config.Cmd stores spaces where the manifest has newlines. bitcoin-knots and fedimint-gateway read as permanently command-drifted over line breaks alone (only the restart-sensitive guard kept them from recreate-looping like electrumx). Normalize both sides the way shell_join does before comparing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6e2d128c51
commit
9ce8a0df05
@ -3882,12 +3882,28 @@ fn command_argv_drifted(
|
|||||||
current_entry: &[String],
|
current_entry: &[String],
|
||||||
current_cmd: &[String],
|
current_cmd: &[String],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
// Mirror shell_join's newline flattening: a YAML-folded manifest script
|
||||||
|
// keeps literal newlines in custom_args, but the quadlet Exec= line (and
|
||||||
|
// therefore Config.Cmd) has them as spaces. Normalize both sides so the
|
||||||
|
// same script never reads as drift over line breaks alone (bitcoin-knots
|
||||||
|
// and fedimint-gateway on .228, 2026-07-08).
|
||||||
|
let norm = |v: &[String]| -> Vec<String> {
|
||||||
|
v.iter().map(|s| s.replace(['\r', '\n'], " ")).collect()
|
||||||
|
};
|
||||||
|
let current_cmd = norm(current_cmd);
|
||||||
|
let expected_args = norm(expected_args);
|
||||||
let Some(expected_entry) = expected_entry else {
|
let Some(expected_entry) = expected_entry else {
|
||||||
// No entrypoint override: the manifest args map to Cmd directly.
|
// No entrypoint override: the manifest args map to Cmd directly.
|
||||||
return !expected_args.is_empty() && current_cmd != expected_args;
|
return !expected_args.is_empty() && current_cmd != expected_args;
|
||||||
};
|
};
|
||||||
let expected: Vec<&String> = expected_entry.iter().chain(expected_args).collect();
|
let expected: Vec<String> = norm(expected_entry)
|
||||||
let actual: Vec<&String> = current_entry.iter().chain(current_cmd).collect();
|
.into_iter()
|
||||||
|
.chain(expected_args.iter().cloned())
|
||||||
|
.collect();
|
||||||
|
let actual: Vec<String> = norm(current_entry)
|
||||||
|
.into_iter()
|
||||||
|
.chain(current_cmd.iter().cloned())
|
||||||
|
.collect();
|
||||||
if expected_args.is_empty() {
|
if expected_args.is_empty() {
|
||||||
// With no manifest args, the container's trailing Cmd may be the
|
// With no manifest args, the container's trailing Cmd may be the
|
||||||
// image's baked-in CMD — only the entrypoint prefix must match.
|
// image's baked-in CMD — only the entrypoint prefix must match.
|
||||||
@ -3943,6 +3959,30 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_drift_tolerates_yaml_folded_newlines() {
|
||||||
|
// A YAML `>-` script with more-indented continuation lines keeps
|
||||||
|
// literal newlines in custom_args; shell_join flattens them to
|
||||||
|
// spaces when writing Exec=, so Config.Cmd has spaces where the
|
||||||
|
// manifest has newlines. Same script — not drift (bitcoin-knots /
|
||||||
|
// fedimint-gateway on .228).
|
||||||
|
let manifest_script = "if [ -z \"$X\" ]; then\n X=1;\nfi; exec daemon";
|
||||||
|
let stored_script = "if [ -z \"$X\" ]; then X=1; fi; exec daemon";
|
||||||
|
assert!(!command_argv_drifted(
|
||||||
|
Some(&sv(&["sh", "-lc"])),
|
||||||
|
&sv(&[manifest_script]),
|
||||||
|
&sv(&["sh"]),
|
||||||
|
&sv(&["-lc", stored_script]),
|
||||||
|
));
|
||||||
|
// A genuinely different script still drifts after normalization.
|
||||||
|
assert!(command_argv_drifted(
|
||||||
|
Some(&sv(&["sh", "-lc"])),
|
||||||
|
&sv(&[manifest_script]),
|
||||||
|
&sv(&["sh"]),
|
||||||
|
&sv(&["-lc", "exec other_daemon"]),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn command_drift_detected_when_argv_actually_differs() {
|
fn command_drift_detected_when_argv_actually_differs() {
|
||||||
assert!(command_argv_drifted(
|
assert!(command_argv_drifted(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user