fix(agent99): deploy backup freshness exporter atomically
This commit is contained in:
@@ -64,6 +64,7 @@ def test_windows99_broker_fetches_exact_gitea_revision_and_is_bounded() -> None:
|
||||
assert '"checkout", "--quiet", $SourceRevision, "--"' in source
|
||||
assert 'executorSha256 = $executorDigest' in source
|
||||
assert 'verifierSha256 = $verifierDigest' in source
|
||||
assert '"scripts/ops/backup-health-textfile-exporter.py"' in source
|
||||
assert "/home/wooo/awoooi" not in source
|
||||
assert '"StrictHostKeyChecking=yes"' in source
|
||||
assert '"NumberOfPasswordPrompts=0"' in source
|
||||
@@ -98,6 +99,9 @@ def test_host110_executor_is_manifest_bound_atomic_and_fail_closed() -> None:
|
||||
assert 'readonly EXPECTED_HOST_IP="192.168.0.110"' in source
|
||||
assert 'readonly EXPECTED_USER="wooo"' in source
|
||||
assert 'readonly DEST_ROOT="/backup/scripts"' in source
|
||||
assert 'readonly EXPORTER_ROOT="/home/wooo/scripts"' in source
|
||||
assert 'readonly EXPORTER_FILE="backup-health-textfile-exporter.py"' in source
|
||||
assert 'readonly -a PAYLOAD_FILES=("${RUNTIME_FILES[@]}" "$EXPORTER_FILE")' in source
|
||||
assert '--source-stage' in source
|
||||
assert 'document.get("executorSha256"' in source
|
||||
assert 'document.get("verifierSha256"' in source
|
||||
@@ -105,16 +109,20 @@ def test_host110_executor_is_manifest_bound_atomic_and_fail_closed() -> None:
|
||||
assert 'verifier_identity_failed' in source
|
||||
assert 'flock -x -w 30 9' in source
|
||||
assert 'legacy_backup_runtime_active' in source
|
||||
assert source.index("for name in \"${RUNTIME_FILES[@]}\"") < source.index("mv -f -- \"$temporary\"")
|
||||
assert source.index("for name in \"${PAYLOAD_FILES[@]}\"") < source.index("mv -f -- \"$temporary\"")
|
||||
assert 'rollback_unverified' in source
|
||||
assert 'failed_rolled_back' in source
|
||||
assert 'working_digest "$dest_path"' in source
|
||||
assert 'PREVIOUS_METADATA' in source
|
||||
assert "stat -c '%u:%g:%a'" in source
|
||||
assert 'os.link(temporary, path)' in source
|
||||
assert 'run_identity_receipt_exists' in source
|
||||
assert 'run_identity_stage_exists' in source
|
||||
assert 'run_identity_rollback_exists' in source
|
||||
assert 'verify-host110-backup-runtime.py' in source
|
||||
assert 'selfIdentityVerified' in source
|
||||
assert '"backupScriptFileCount": 17' in source
|
||||
assert '"backupHealthExporterIncluded": True' in source
|
||||
assert '"productionServiceRestarted": False' in source
|
||||
assert '"secretValuesRead": False' in source
|
||||
assert "sudo " not in source
|
||||
@@ -139,12 +147,16 @@ def test_independent_verifier_accepts_exact_bundle_without_writing(tmp_path, mon
|
||||
verifier = _load_verifier()
|
||||
destination = tmp_path / "scripts"
|
||||
destination.mkdir()
|
||||
exporter_destination = tmp_path / verifier.EXPORTER_FILE
|
||||
rows = []
|
||||
for name in verifier.EXPECTED_FILES:
|
||||
for name in verifier.EXPECTED_BACKUP_FILES:
|
||||
path = destination / name
|
||||
path.write_text(f"runtime:{name}\n", encoding="utf-8")
|
||||
path.chmod(0o755)
|
||||
rows.append({"name": name, "sha256": _sha256(path)})
|
||||
exporter_destination.write_text("exporter\n", encoding="utf-8")
|
||||
exporter_destination.chmod(0o755)
|
||||
rows.append({"name": verifier.EXPORTER_FILE, "sha256": _sha256(exporter_destination)})
|
||||
verifier_digest = _sha256(VERIFIER)
|
||||
source_revision = "a" * 40
|
||||
run_id = "unit-exact"
|
||||
@@ -159,8 +171,12 @@ def test_independent_verifier_accepts_exact_bundle_without_writing(tmp_path, mon
|
||||
}
|
||||
manifest_path = tmp_path / "manifest.json"
|
||||
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
||||
before = {path.name: (path.stat().st_mtime_ns, _sha256(path)) for path in destination.iterdir()}
|
||||
before = {
|
||||
path.name: (path.stat().st_mtime_ns, _sha256(path))
|
||||
for path in [*destination.iterdir(), exporter_destination]
|
||||
}
|
||||
monkeypatch.setattr(verifier, "EXPECTED_DESTINATION", destination)
|
||||
monkeypatch.setattr(verifier, "EXPECTED_EXPORTER_DESTINATION", exporter_destination)
|
||||
monkeypatch.setattr(
|
||||
sys,
|
||||
"argv",
|
||||
@@ -170,6 +186,8 @@ def test_independent_verifier_accepts_exact_bundle_without_writing(tmp_path, mon
|
||||
str(manifest_path),
|
||||
"--destination",
|
||||
str(destination),
|
||||
"--exporter-destination",
|
||||
str(exporter_destination),
|
||||
"--source-revision",
|
||||
source_revision,
|
||||
"--run-id",
|
||||
@@ -182,8 +200,14 @@ def test_independent_verifier_accepts_exact_bundle_without_writing(tmp_path, mon
|
||||
verifier.main()
|
||||
|
||||
result = json.loads(capsys.readouterr().out)
|
||||
after = {path.name: (path.stat().st_mtime_ns, _sha256(path)) for path in destination.iterdir()}
|
||||
after = {
|
||||
path.name: (path.stat().st_mtime_ns, _sha256(path))
|
||||
for path in [*destination.iterdir(), exporter_destination]
|
||||
}
|
||||
assert result["ok"] is True
|
||||
assert result["fileCount"] == 18
|
||||
assert result["backupScriptFileCount"] == 17
|
||||
assert result["backupHealthExporterIncluded"] is True
|
||||
assert result["remoteWritePerformed"] is False
|
||||
assert result["selfIdentityVerified"] is True
|
||||
assert before == after
|
||||
@@ -193,13 +217,17 @@ def test_independent_verifier_rejects_drifted_bundle(tmp_path, monkeypatch, caps
|
||||
verifier = _load_verifier()
|
||||
destination = tmp_path / "scripts"
|
||||
destination.mkdir()
|
||||
exporter_destination = tmp_path / verifier.EXPORTER_FILE
|
||||
rows = []
|
||||
for name in verifier.EXPECTED_FILES:
|
||||
for name in verifier.EXPECTED_BACKUP_FILES:
|
||||
path = destination / name
|
||||
path.write_text(f"runtime:{name}\n", encoding="utf-8")
|
||||
path.chmod(0o755)
|
||||
rows.append({"name": name, "sha256": _sha256(path)})
|
||||
(destination / verifier.EXPECTED_FILES[0]).write_text("drifted\n", encoding="utf-8")
|
||||
exporter_destination.write_text("exporter\n", encoding="utf-8")
|
||||
exporter_destination.chmod(0o755)
|
||||
rows.append({"name": verifier.EXPORTER_FILE, "sha256": _sha256(exporter_destination)})
|
||||
(destination / verifier.EXPECTED_BACKUP_FILES[0]).write_text("drifted\n", encoding="utf-8")
|
||||
verifier_digest = _sha256(VERIFIER)
|
||||
source_revision = "d" * 40
|
||||
run_id = "unit-drift"
|
||||
@@ -214,6 +242,7 @@ def test_independent_verifier_rejects_drifted_bundle(tmp_path, monkeypatch, caps
|
||||
}
|
||||
encoded = base64.b64encode(json.dumps(manifest).encode()).decode()
|
||||
monkeypatch.setattr(verifier, "EXPECTED_DESTINATION", destination)
|
||||
monkeypatch.setattr(verifier, "EXPECTED_EXPORTER_DESTINATION", exporter_destination)
|
||||
monkeypatch.setattr(
|
||||
sys,
|
||||
"argv",
|
||||
@@ -223,6 +252,8 @@ def test_independent_verifier_rejects_drifted_bundle(tmp_path, monkeypatch, caps
|
||||
encoded,
|
||||
"--destination",
|
||||
str(destination),
|
||||
"--exporter-destination",
|
||||
str(exporter_destination),
|
||||
"--source-revision",
|
||||
source_revision,
|
||||
"--run-id",
|
||||
|
||||
Reference in New Issue
Block a user