fix(api): verify production deploy image readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 39s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 39s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
This commit is contained in:
@@ -118,29 +118,62 @@ def _enrich_runtime_build_readback(payload: dict[str, Any]) -> None:
|
||||
else "runtime_build_diverges_from_committed_deploy_readback"
|
||||
)
|
||||
|
||||
if source_matches_runtime and image_matches_runtime:
|
||||
desired_tag = os.getenv("AWOOOI_DESIRED_API_IMAGE_TAG", "").strip().lower()
|
||||
if not _SHA_RE.fullmatch(desired_tag):
|
||||
readback["desired_main_api_image_tag_readback_status"] = "unavailable"
|
||||
_mark_runtime_image_tag_blocked(
|
||||
payload,
|
||||
blocker="gitea_main_desired_api_image_tag_readback_unavailable",
|
||||
next_action=(
|
||||
"redeploy_with_awoooi_desired_api_image_tag_env_matching_gitops"
|
||||
"_desired_api_tag"
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
# The runtime build SHA is evidence about the currently running image, not
|
||||
# permission to rewrite source-control truth. If it diverges from the
|
||||
# committed deploy snapshot, fail closed so Workbench cannot report a false
|
||||
# "image tag matches main" closure while production routes are stale.
|
||||
readback["desired_main_api_image_tag_source"] = "gitops_deployment_env"
|
||||
readback["desired_main_api_image_tag_readback_status"] = "ok"
|
||||
readback["desired_main_api_image_tag_sha"] = desired_tag
|
||||
readback["desired_main_api_image_tag_short_sha"] = desired_tag[:10]
|
||||
image_matches_main = build_sha == desired_tag
|
||||
readback["production_image_tag_matches_main"] = image_matches_main
|
||||
rollups["source_control_main_ready"] = True
|
||||
rollups["production_image_tag_matches_main"] = image_matches_main
|
||||
if image_matches_main:
|
||||
return
|
||||
|
||||
_mark_runtime_image_tag_blocked(
|
||||
payload,
|
||||
blocker="production_runtime_image_tag_does_not_match_gitea_main_desired_tag",
|
||||
next_action=(
|
||||
"complete_cd_rollout_until_runtime_build_commit_matches_gitops"
|
||||
"_desired_api_image_tag_env"
|
||||
),
|
||||
source_control_main_ready=True,
|
||||
)
|
||||
|
||||
|
||||
def _mark_runtime_image_tag_blocked(
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
blocker: str,
|
||||
next_action: str,
|
||||
source_control_main_ready: bool = False,
|
||||
) -> None:
|
||||
payload["status"] = "blocked_production_runtime_image_tag_not_verified"
|
||||
readback = _dict(payload.get("readback"))
|
||||
readback["production_image_tag_matches_main"] = False
|
||||
rollups["source_control_main_ready"] = False
|
||||
blockers = _list(payload.setdefault("blockers", []))
|
||||
if blocker not in blockers:
|
||||
blockers.append(blocker)
|
||||
next_actions = _list(payload.setdefault("next_actions", []))
|
||||
if next_action not in next_actions:
|
||||
next_actions.append(next_action)
|
||||
rollups = _dict(payload.get("rollups"))
|
||||
rollups["hard_blocker_count"] = len(blockers)
|
||||
rollups["next_action_count"] = len(next_actions)
|
||||
rollups["source_control_main_ready"] = source_control_main_ready
|
||||
rollups["production_image_tag_matches_main"] = False
|
||||
payload["status"] = "runtime_build_readback_stale"
|
||||
_append_unique(
|
||||
payload,
|
||||
"blockers",
|
||||
"runtime_build_commit_sha_does_not_match_committed_production_readback",
|
||||
)
|
||||
_append_unique(
|
||||
payload,
|
||||
"next_actions",
|
||||
"refresh_production_deploy_readback_from_current_main_and_runtime_route_smoke",
|
||||
)
|
||||
rollups["hard_blocker_count"] = len(_list(payload.get("blockers")))
|
||||
rollups["next_action_count"] = len(_list(payload.get("next_actions")))
|
||||
|
||||
|
||||
def _require_no_internal_network_literals(value: Any, label: str) -> None:
|
||||
|
||||
@@ -311,6 +311,18 @@ def build_delivery_closure_workbench(
|
||||
)
|
||||
is True
|
||||
),
|
||||
"desired_main_api_image_tag_short_sha": str(
|
||||
production_deploy_readback.get(
|
||||
"desired_main_api_image_tag_short_sha"
|
||||
)
|
||||
or ""
|
||||
),
|
||||
"desired_main_api_image_tag_readback_status": str(
|
||||
production_deploy_readback.get(
|
||||
"desired_main_api_image_tag_readback_status"
|
||||
)
|
||||
or ""
|
||||
),
|
||||
"production_image_tag_matches_main": production_deploy_readback.get(
|
||||
"production_image_tag_matches_main"
|
||||
)
|
||||
@@ -1673,6 +1685,16 @@ def build_delivery_closure_workbench(
|
||||
)
|
||||
is True
|
||||
),
|
||||
"production_deploy_desired_main_api_image_tag_short_sha": str(
|
||||
production_deploy_readback.get("desired_main_api_image_tag_short_sha")
|
||||
or ""
|
||||
),
|
||||
"production_deploy_desired_main_api_image_tag_readback_status": str(
|
||||
production_deploy_readback.get(
|
||||
"desired_main_api_image_tag_readback_status"
|
||||
)
|
||||
or ""
|
||||
),
|
||||
"production_deploy_governance_fields_present": production_deploy_rollups.get(
|
||||
"production_governance_fields_present"
|
||||
)
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from src.services.awoooi_production_deploy_readback_blocker import (
|
||||
load_latest_awoooi_production_deploy_readback_blocker,
|
||||
)
|
||||
from src.services import awoooi_production_deploy_readback_blocker as service
|
||||
|
||||
|
||||
def test_production_deploy_readback_uses_runtime_build_commit(monkeypatch):
|
||||
_COMMITTED_SNAPSHOT_SHA = "a70c6756d9e76c33143676eef82bab7a49ac1839"
|
||||
|
||||
|
||||
def test_production_deploy_readback_verifies_runtime_build_against_gitops_desired(
|
||||
monkeypatch,
|
||||
):
|
||||
build_sha = "0123456789abcdef0123456789abcdef01234567"
|
||||
monkeypatch.setenv("AWOOOI_BUILD_COMMIT_SHA", build_sha)
|
||||
monkeypatch.setenv("AWOOOI_DESIRED_API_IMAGE_TAG", build_sha)
|
||||
|
||||
payload = load_latest_awoooi_production_deploy_readback_blocker()
|
||||
payload = service.load_latest_awoooi_production_deploy_readback_blocker()
|
||||
readback = payload["readback"]
|
||||
rollups = payload["rollups"]
|
||||
|
||||
assert readback["runtime_build_commit_sha"] == build_sha
|
||||
assert readback["runtime_build_commit_short_sha"] == build_sha[:10]
|
||||
assert readback["observed_source_control_main_sha"] == (
|
||||
"a70c6756d9e76c33143676eef82bab7a49ac1839"
|
||||
)
|
||||
assert readback["production_image_tag_sha"] == (
|
||||
"a70c6756d9e76c33143676eef82bab7a49ac1839"
|
||||
)
|
||||
assert readback["production_image_tag_sha"] == _COMMITTED_SNAPSHOT_SHA
|
||||
assert (
|
||||
readback["runtime_build_matches_committed_source_control_readback"]
|
||||
is False
|
||||
@@ -32,23 +31,21 @@ def test_production_deploy_readback_uses_runtime_build_commit(monkeypatch):
|
||||
assert readback["runtime_build_readback_status"] == (
|
||||
"runtime_build_diverges_from_committed_deploy_readback"
|
||||
)
|
||||
assert readback["production_image_tag_matches_main"] is False
|
||||
assert payload["status"] == "runtime_build_readback_stale"
|
||||
assert rollups["source_control_main_ready"] is False
|
||||
assert rollups["production_image_tag_matches_main"] is False
|
||||
assert rollups["hard_blocker_count"] == 1
|
||||
assert payload["blockers"] == [
|
||||
"runtime_build_commit_sha_does_not_match_committed_production_readback"
|
||||
]
|
||||
assert readback["desired_main_api_image_tag_sha"] == build_sha
|
||||
assert readback["desired_main_api_image_tag_source"] == "gitops_deployment_env"
|
||||
assert readback["desired_main_api_image_tag_readback_status"] == "ok"
|
||||
assert readback["production_image_tag_matches_main"] is True
|
||||
assert payload["status"] == "closure_verified"
|
||||
assert rollups["production_image_tag_matches_main"] is True
|
||||
assert rollups["hard_blocker_count"] == 0
|
||||
|
||||
|
||||
def test_production_deploy_readback_keeps_closure_when_runtime_matches_snapshot(
|
||||
monkeypatch,
|
||||
):
|
||||
build_sha = "a70c6756d9e76c33143676eef82bab7a49ac1839"
|
||||
def test_production_deploy_readback_keeps_committed_snapshot_evidence(monkeypatch):
|
||||
build_sha = _COMMITTED_SNAPSHOT_SHA
|
||||
monkeypatch.setenv("AWOOOI_BUILD_COMMIT_SHA", build_sha)
|
||||
monkeypatch.setenv("AWOOOI_DESIRED_API_IMAGE_TAG", build_sha)
|
||||
|
||||
payload = load_latest_awoooi_production_deploy_readback_blocker()
|
||||
payload = service.load_latest_awoooi_production_deploy_readback_blocker()
|
||||
readback = payload["readback"]
|
||||
rollups = payload["rollups"]
|
||||
|
||||
@@ -58,19 +55,60 @@ def test_production_deploy_readback_keeps_closure_when_runtime_matches_snapshot(
|
||||
assert readback["runtime_build_readback_status"] == (
|
||||
"matches_committed_deploy_readback"
|
||||
)
|
||||
assert readback["desired_main_api_image_tag_sha"] == build_sha
|
||||
assert readback["production_image_tag_matches_main"] is True
|
||||
assert payload["status"] == "closure_verified"
|
||||
assert rollups["production_image_tag_matches_main"] is True
|
||||
assert rollups["hard_blocker_count"] == 0
|
||||
|
||||
|
||||
def test_production_deploy_readback_blocks_runtime_build_mismatch(monkeypatch):
|
||||
build_sha = "0123456789abcdef0123456789abcdef01234567"
|
||||
desired_sha = "abcdef0123456789abcdef0123456789abcdef01"
|
||||
monkeypatch.setenv("AWOOOI_BUILD_COMMIT_SHA", build_sha)
|
||||
monkeypatch.setenv("AWOOOI_DESIRED_API_IMAGE_TAG", desired_sha)
|
||||
|
||||
payload = service.load_latest_awoooi_production_deploy_readback_blocker()
|
||||
readback = payload["readback"]
|
||||
rollups = payload["rollups"]
|
||||
|
||||
assert payload["status"] == "blocked_production_runtime_image_tag_not_verified"
|
||||
assert readback["runtime_build_commit_sha"] == build_sha
|
||||
assert readback["runtime_build_readback_status"] == (
|
||||
"runtime_build_diverges_from_committed_deploy_readback"
|
||||
)
|
||||
assert readback["desired_main_api_image_tag_sha"] == desired_sha
|
||||
assert readback["production_image_tag_matches_main"] is False
|
||||
assert rollups["source_control_main_ready"] is True
|
||||
assert rollups["production_image_tag_matches_main"] is False
|
||||
assert rollups["hard_blocker_count"] == 1
|
||||
assert "production_runtime_image_tag_does_not_match_gitea_main_desired_tag" in (
|
||||
payload["blockers"]
|
||||
)
|
||||
|
||||
|
||||
def test_production_deploy_readback_blocks_unavailable_gitops_desired(monkeypatch):
|
||||
build_sha = "0123456789abcdef0123456789abcdef01234567"
|
||||
monkeypatch.setenv("AWOOOI_BUILD_COMMIT_SHA", build_sha)
|
||||
monkeypatch.delenv("AWOOOI_DESIRED_API_IMAGE_TAG", raising=False)
|
||||
|
||||
payload = service.load_latest_awoooi_production_deploy_readback_blocker()
|
||||
readback = payload["readback"]
|
||||
|
||||
assert payload["status"] == "blocked_production_runtime_image_tag_not_verified"
|
||||
assert readback["desired_main_api_image_tag_readback_status"] == "unavailable"
|
||||
assert readback["production_image_tag_matches_main"] is False
|
||||
assert payload["rollups"]["source_control_main_ready"] is False
|
||||
assert "gitea_main_desired_api_image_tag_readback_unavailable" in payload[
|
||||
"blockers"
|
||||
]
|
||||
|
||||
|
||||
def test_production_deploy_readback_ignores_non_sha_runtime_value(monkeypatch):
|
||||
monkeypatch.setenv("AWOOOI_BUILD_COMMIT_SHA", "none")
|
||||
|
||||
payload = load_latest_awoooi_production_deploy_readback_blocker()
|
||||
payload = service.load_latest_awoooi_production_deploy_readback_blocker()
|
||||
readback = payload["readback"]
|
||||
|
||||
assert "runtime_build_commit_sha" not in readback
|
||||
assert readback["production_image_tag_sha"] == (
|
||||
"a70c6756d9e76c33143676eef82bab7a49ac1839"
|
||||
)
|
||||
assert readback["production_image_tag_sha"] == _COMMITTED_SNAPSHOT_SHA
|
||||
|
||||
Reference in New Issue
Block a user