diff --git a/scripts/ci/test-deploy-recovery.py b/scripts/ci/test-deploy-recovery.py index d5c6bd18..ae1d30ed 100755 --- a/scripts/ci/test-deploy-recovery.py +++ b/scripts/ci/test-deploy-recovery.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Run actual workflow Bash against offline CLI/HTTP boundaries only.""" +import hashlib import json from pathlib import Path import re @@ -19,6 +20,10 @@ PRE_N0 = (ROOT / "ops/rollback/pre-n0-compose.yaml").read_bytes() STOP_LOGS = (ROOT / "ops/rollback/pre-n0-stop-local-logs.yaml").read_bytes() CURRENT = (ROOT / "docker-compose.dstack-postgres.yaml").read_bytes() +# Ordinary (pre-N0) template from main before #309: the captured rollback compose with its digest pin +# reverted to the :dstack tag. The workflow accepts these bytes by raw hash. +PRE_N0_TEMPLATE = PRE_N0.replace(b"@" + DIGEST.encode(), b":dstack") +assert hashlib.sha256(PRE_N0_TEMPLATE).hexdigest() == "28302d6761d841b7cc5c11314aed2d377735e6bbdcaf85972a752e03bc9fdc93" CANARIES = [] @@ -247,7 +252,7 @@ def run_deploy(self, compose=CURRENT, changes=None, *, fail_at="", artifact_erro "compose": compose_path.read_bytes(), "summary": report, "logs": logs} def test_recovery_current_pre_n0_and_reviewed_n0(self): - for compose in (CURRENT, N0): + for compose in (PRE_N0_TEMPLATE, N0): with self.subTest(n0=compose == N0): result = self.run_deploy(compose) self.assertTrue(result["success"], "Authorized same-image recovery must work") @@ -257,7 +262,7 @@ def test_recovery_current_pre_n0_and_reviewed_n0(self): self.assertNotIn("1.20.7", result["logs"] + result["summary"]) def test_future_digest_gets_preflight_and_general_quota_restore(self): - result = self.run_deploy(changes={"deploy_image_digest": "sha256:" + "a" * 64, + result = self.run_deploy(PRE_N0_TEMPLATE, {"deploy_image_digest": "sha256:" + "a" * 64, "image_version": "1.20.8", "rollback_image_version": "1.20.8", "rollback_image_revision": "a" * 40}) self.assertTrue(result["success"]) self.assertIn("sealed-preflight", result["events"]) @@ -274,9 +279,9 @@ def test_explicit_rollback_privacy_variants(self): self.assertIn(mode, result["summary"]) def test_missing_authorization_never_reaches_any_mutation(self): - for compose, changes, mode in ((CURRENT, {}, ""), (N0, {}, ""), + for compose, changes, mode in ((PRE_N0_TEMPLATE, {}, ""), (N0, {}, ""), (N0, {"rollback_pre_n0": "true", "rollback_privacy_mode": "keep-json-file"}, "keep-json-file"), - (CURRENT, {"deploy_image_digest": "sha256:" + "a" * 64, "image_version": "1.20.8", + (PRE_N0_TEMPLATE, {"deploy_image_digest": "sha256:" + "a" * 64, "image_version": "1.20.8", "rollback_image_version": "1.20.8", "rollback_image_revision": "a" * 40}, "")): result = self.run_deploy(compose, {**changes, "authorize_quota_values": "false"}, approved_privacy=mode) self.assertFalse(result["success"]) @@ -327,7 +332,7 @@ def test_wrong_oci_labels_refuse_before_compose_or_cvm_mutation(self): self.assertEqual(result["compose"], N0) def test_normal_build_deployment_keeps_preflight_and_general_quota(self): - result = self.run_deploy(changes={"deploy_image_digest": "", "image_version": "1.20.7", + result = self.run_deploy(PRE_N0_TEMPLATE, {"deploy_image_digest": "", "image_version": "1.20.7", "rollback_image_version": "", "rollback_image_revision": ""}, build_digest="sha256:" + "f" * 64) self.assertTrue(result["success"]) @@ -343,6 +348,12 @@ def test_forward_n0_lock_and_captured_rollback_are_not_substitutable(self): self.assertFalse(result["success"]) self.assertNotIn("cvm-deploy", result["events"]) self.assertEqual(result["compose"], N0) + result = self.run_deploy(N0, {"deploy_image_digest": "", "image_version": "1.20.7", + "rollback_image_version": "", "rollback_image_revision": ""}, + build_digest="sha256:" + "f" * 64) + self.assertFalse(result["success"]) + self.assertNotIn("cvm-deploy", result["events"]) + self.assertEqual(result["compose"], N0) def test_last_restart_precedes_health_inventory_and_quota(self): result = self.run_deploy(N0, {"rollback_pre_n0": "true", "rollback_privacy_mode": "keep-json-file"}, approved_privacy="keep-json-file")