Files
awoooi/scripts/reboot-recovery/validate-windows99-console-verify-artifact.py
Your Name 30c89a0cab
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m1s
CD Pipeline / build-and-deploy (push) Successful in 4m28s
CD Pipeline / post-deploy-checks (push) Successful in 1m45s
fix(reboot): support windows99 verifier via host
2026-07-03 13:53:28 +08:00

164 lines
5.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Validate no-secret Windows99 VMware Verify stdout captured from console."""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
SCHEMA_VERSION = "windows99_console_verify_artifact_validator_v1"
DEFAULT_REQUIRED_ALIASES = ("110", "188", "120", "121", "112")
SENSITIVE_RE = re.compile(
r"(?i)(password|passwd|secret|token|authorization|cookie|private[_ -]?key)\s*[=:]"
)
def _normalize(text: str) -> list[str]:
text = text.replace("\r\n", "\n").replace("\r", "\n")
lines = [line.strip() for line in text.splitlines()]
if "remote_verify_output_begin" in lines and "remote_verify_output_end" in lines:
begin = lines.index("remote_verify_output_begin") + 1
end = lines.index("remote_verify_output_end")
lines = lines[begin:end]
return [line for line in lines if line]
def _value_for(lines: list[str], key: str) -> str:
prefix = f"{key}="
for line in lines:
if line.startswith(prefix):
return line.split("=", 1)[1]
return ""
def _line_matches(lines: list[str], pattern: str) -> bool:
compiled = re.compile(pattern)
return any(compiled.search(line) for line in lines)
def _csv(items: list[str]) -> str:
return ",".join(items)
def validate(lines: list[str], required_aliases: tuple[str, ...]) -> tuple[int, list[str]]:
missing_fields: list[str] = []
secret_like_lines = [line for line in lines if SENSITIVE_RE.search(line)]
required_exact_keys = [
"AWOOOI_WINDOWS99_VMWARE_AUTOSTART",
"MODE",
"VMRUN_PRESENT",
"VMWARE_AUTOSTART_CONFIG_READY",
"VMWARE_AUTOSTART_POWER_READY",
"WINDOWS_UPDATE_NO_AUTO_REBOOT_READY",
"VMWARE_AUTOSTART_VERIFY_READY",
]
for key in required_exact_keys:
if not _value_for(lines, key):
missing_fields.append(key)
if _value_for(lines, "AWOOOI_WINDOWS99_VMWARE_AUTOSTART") != "1":
missing_fields.append("AWOOOI_WINDOWS99_VMWARE_AUTOSTART=1")
if _value_for(lines, "MODE") != "Verify":
missing_fields.append("MODE=Verify")
if not any(line.startswith("VMWARE_SERVICE ") for line in lines):
missing_fields.append("VMWARE_SERVICE")
if not any(line.startswith("VMWARE_AUTOSTART_TASK ") for line in lines):
missing_fields.append("VMWARE_AUTOSTART_TASK")
if not any(line.startswith("WINDOWS_UPDATE_POLICY ") for line in lines):
missing_fields.append("WINDOWS_UPDATE_POLICY")
missing_vmx_aliases: list[str] = []
powered_off_aliases: list[str] = []
for alias in required_aliases:
vmx_pattern = rf"^VMX alias={re.escape(alias)}\s+.*\spresent=([01])$"
power_pattern = rf"^VM_POWER alias={re.escape(alias)}\s+.*\srunning=([01])$"
vmx_line = next((line for line in lines if re.search(vmx_pattern, line)), "")
power_line = next((line for line in lines if re.search(power_pattern, line)), "")
if not vmx_line:
missing_fields.append(f"VMX alias={alias}")
elif not _line_matches([vmx_line], rf"\spresent=1$"):
missing_vmx_aliases.append(alias)
if not power_line:
missing_fields.append(f"VM_POWER alias={alias}")
elif not _line_matches([power_line], rf"\srunning=1$"):
powered_off_aliases.append(alias)
status = "ready_console_verify_artifact"
exit_code = 0
if secret_like_lines:
status = "blocked_secret_like_console_output"
exit_code = 76
elif missing_fields:
status = "blocked_missing_required_console_verify_fields"
exit_code = 75
elif _value_for(lines, "VMWARE_AUTOSTART_VERIFY_READY") != "1":
status = "valid_console_verify_artifact_vmware_not_ready"
summary = [
f"schema_version={SCHEMA_VERSION}",
f"status={status}",
f"required_aliases={_csv(list(required_aliases))}",
f"missing_required_field_count={len(missing_fields)}",
f"missing_required_fields={_csv(missing_fields)}",
f"secret_like_line_count={len(secret_like_lines)}",
f"missing_vmx_aliases={_csv(missing_vmx_aliases)}",
f"powered_off_aliases={_csv(powered_off_aliases)}",
f"vmware_autostart_verify_ready={_value_for(lines, 'VMWARE_AUTOSTART_VERIFY_READY') or 'unknown'}",
"secret_value_read=false",
"password_prompt_allowed=false",
"remote_write_performed=false",
"host_reboot_performed=false",
"vm_power_change_performed=false",
"windows_update_policy_apply_performed=false",
]
return exit_code, summary
def main() -> int:
parser = argparse.ArgumentParser(
description=(
"Validate no-secret stdout from windows99-vmware-autostart.ps1 -Mode Verify "
"captured through RDP / Hyper-V / console."
)
)
parser.add_argument("--input", required=True, help="Raw console stdout file.")
parser.add_argument(
"--output",
help="Optional normalized artifact for --windows99-vmware-file.",
)
parser.add_argument(
"--required-aliases",
default=",".join(DEFAULT_REQUIRED_ALIASES),
help="Comma or space separated required VM aliases.",
)
args = parser.parse_args()
required_aliases = tuple(
item
for item in re.split(r"[\s,]+", args.required_aliases.strip())
if item
) or DEFAULT_REQUIRED_ALIASES
input_path = Path(args.input)
raw_text = input_path.read_text(encoding="utf-8", errors="replace")
lines = _normalize(raw_text)
exit_code, summary = validate(lines, required_aliases)
if exit_code == 0 and args.output:
output_path = Path(args.output)
output_path.write_text("\n".join(lines + [""]) , encoding="utf-8")
for line in summary:
print(line)
if args.output:
print(f"normalized_output={args.output if exit_code == 0 else ''}")
return exit_code
if __name__ == "__main__":
raise SystemExit(main())