Reject ambiguous configuration in passive VM disk preflight

Do not report an absent lock or agent setting after malformed current-configuration output. Preserve passive bounded host reads, private output, unknown well-formed fields and the distinction from guest responsiveness or thaw.

Contract-Neutral: The operator-only helper now enforces its existing incomplete-read failure semantics; polling, guest-agent admission, API and storage contracts are unchanged.
Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-04 04:46:02 +01:00
parent a6b83db5bb
commit adcd0ddf01
2 changed files with 48 additions and 2 deletions
+10 -2
View File
@@ -67,9 +67,17 @@ field_seen=false
agent_value=''
lock_value=''
while IFS= read -r line; do
if [[ $line =~ ^[A-Za-z][A-Za-z0-9_-]*: ]]; then
field_seen=true
if [[ $line =~ ^[[:space:]]*$ || $line =~ ^[[:space:]]*# ]]; then
continue
fi
# qm's fields are unindented lowercase keys. Do not let one valid field
# turn malformed/truncated lock or agent lines into an absent setting.
# Unknown well-formed fields remain valid; never echo an unparsed line.
if [[ ! $line =~ ^[a-z][a-z0-9_-]*: ]]; then
printf '%s\n' 'VM current configuration contains an unrecognised line; preflight incomplete.' >&2
exit 1
fi
field_seen=true
case "$line" in
agent:*)
if [[ $agent_seen == true ]]; then
+38
View File
@@ -185,6 +185,44 @@ class VMDiskDiagnosticsTest(unittest.TestCase):
self.assertNotIn("preflight completed", result.stdout)
self.assert_passive(calls, operations=READS[:1] if "status_output" in settings else READS)
def test_malformed_lines_cannot_hide_agent_or_lock_configuration(self):
lines = (
" lock: backup", "\tlock: backup", "lock=backup", "lock : backup",
"Lock: backup", "LOCK: backup", "lock", "agent 1", "Agent: 1",
"warning " + PRIVATE,
)
for line in lines:
for suffix in ("", "\n"):
with self.subTest(line=line, suffix=suffix):
config = f"agent: 1\nname: {PRIVATE}\n{line}{suffix}"
result, calls = self.exercise(settings={"config_output": config})
self.assertEqual(result.returncode, 1, result.stdout)
self.assertIn("unrecognised line; preflight incomplete", result.stderr)
self.assertNotIn("Guest agent configured:", result.stdout)
self.assertNotIn("VM lock:", result.stdout)
self.assertNotIn("preflight completed", result.stdout)
self.assert_passive(calls)
def test_blank_comments_and_unknown_well_formed_fields_remain_passive(self):
for agent in (None, "0", "1"):
with self.subTest(agent=agent):
config = (f"\n# {PRIVATE}\n \t\nfuture_read_option: {PRIVATE}\n"
f"\t# lock: backup {PRIVATE}\nname: {PRIVATE}\n")
if agent is not None:
config += f"agent: {agent}\n"
result, calls = self.exercise(settings={"config_output": config})
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("Guest agent configured: enabled" if agent == "1"
else "Guest agent configured: disabled", result.stdout)
self.assertIn("VM lock: not reported", result.stdout)
self.assert_passive(calls)
result, calls = self.exercise(settings={"config_output": f"\n# {PRIVATE}\n \t\n"})
self.assertEqual(result.returncode, 1)
self.assertIn("preflight incomplete", result.stderr)
self.assertNotIn("VM lock:", result.stdout)
self.assert_passive(calls)
def test_help_and_invalid_arguments_never_contact_proxmox(self):
for args in ([], ["0"], ["-1"], ["100;touch BAD"], ["100", "--probe"],
["9999999999"], ["--probe"], ["--help"]):