Coverage for scripts / live_release_validation / cleanup / retained.py: 100.00%
85 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-10-28 16:19 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-10-28 16:19 +0000
1"""Schedule and account for intentionally retained resources."""
3from __future__ import annotations
5import copy
6import json
7from typing import Any
9from botocore.exceptions import ClientError
11from ..cleanup.ecr import _cleanup_new_ecr_images, _cleanup_new_ecr_repositories
12from ..cleanup.log_groups import (
13 _cleanup_owned_log_groups,
14)
15from ..cleanup.transaction_search import (
16 TransactionSearchRestoreError,
17 _restore_transaction_search,
18)
19from ..constants import (
20 _KMS_PENDING_WINDOW_DAYS,
21 _RUN_STACK_TAG,
22 _LogGroupCleanupError,
23)
24from ..models import RunContext, utc_now
25from ..ownership.kms import (
26 _kms_tags,
27 _validated_owned_kms_identity,
28)
31def _schedule_retained_kms_keys(ctx: RunContext) -> dict[str, Any]:
32 records = ctx.checkpoint.state.get("owned_kms_keys", [])
33 retained_records = [
34 record
35 for record in records
36 if _validated_owned_kms_identity(ctx, record)[3] == "harness-schedule"
37 ]
38 if retained_records and not ctx.settings.confirm_kms_key_deletion:
39 raise RuntimeError("Retained KMS keys exist but this identity did not confirm key deletion")
40 results: list[dict[str, Any]] = []
41 for record in records:
42 region, key_id, arn, cleanup_policy = _validated_owned_kms_identity(ctx, record)
43 record.setdefault("cleanup_policy", cleanup_policy)
44 kms = ctx.session.client("kms", region_name=region)
45 try:
46 metadata = kms.describe_key(KeyId=key_id).get("KeyMetadata", {})
47 except ClientError as exc:
48 if exc.response.get("Error", {}).get("Code") == "NotFoundException":
49 record["scheduled"] = True
50 record["deleted"] = True
51 results.append(
52 {
53 "arn": arn,
54 "cleanup_policy": cleanup_policy,
55 "already_absent": True,
56 }
57 )
58 ctx.persist()
59 continue
60 raise
61 if metadata.get("Arn") != arn:
62 raise RuntimeError(f"KMS key ARN changed for {key_id}")
63 tags = _kms_tags(kms, key_id)
64 if tags.get(_RUN_STACK_TAG) != record["run_tag"]:
65 raise RuntimeError(f"KMS run ownership changed for {arn}")
67 state = str(metadata.get("KeyState") or "")
68 if state != "PendingDeletion" and cleanup_policy == "harness-schedule":
69 if state not in {"Enabled", "Disabled"}:
70 raise RuntimeError(f"KMS key {arn} is {state}; refusing to schedule deletion")
71 kms.schedule_key_deletion(
72 KeyId=key_id,
73 PendingWindowInDays=_KMS_PENDING_WINDOW_DAYS,
74 )
75 metadata = kms.describe_key(KeyId=key_id).get("KeyMetadata", {})
76 state = str(metadata.get("KeyState") or "")
77 if state != "PendingDeletion":
78 raise RuntimeError(
79 f"Expected {cleanup_policy} KMS key {arn} to be PendingDeletion; found {state}"
80 )
81 deletion_date = metadata.get("DeletionDate")
82 record["scheduled"] = True
83 record["deletion_date"] = deletion_date.isoformat() if deletion_date is not None else None
84 if not record["deletion_date"]:
85 raise RuntimeError(f"Pending-deletion KMS key omitted its deletion date: {arn}")
86 results.append(
87 {
88 "arn": arn,
89 "state": state,
90 "cleanup_policy": cleanup_policy,
91 "deletion_date": record["deletion_date"],
92 }
93 )
94 ctx.persist()
95 return {
96 "keys": results,
97 "deletion_window": {
98 "harness_schedule_days": _KMS_PENDING_WINDOW_DAYS,
99 "cloudformation_delete": "observed per key deletion_date",
100 },
101 }
104def _retained_resource_cleanup(ctx: RunContext) -> dict[str, Any]:
105 result: dict[str, Any] = {"started_at": utc_now(), "errors": []}
106 try:
107 result["cloudwatch_logs"] = _cleanup_owned_log_groups(ctx)
108 except Exception as exc: # preserve partial evidence
109 if isinstance(exc, _LogGroupCleanupError):
110 result["cloudwatch_logs"] = copy.deepcopy(exc.details)
111 result["errors"].append(
112 {"phase": "cloudwatch-logs", "error": f"{type(exc).__name__}: {exc}"}
113 )
114 try:
115 result["ecr_images"] = _cleanup_new_ecr_images(ctx)
116 except Exception as exc: # preserve partial evidence
117 result["errors"].append({"phase": "ecr-images", "error": f"{type(exc).__name__}: {exc}"})
118 try:
119 result["ecr_repositories"] = _cleanup_new_ecr_repositories(ctx)
120 except Exception as exc: # preserve partial evidence
121 result["errors"].append(
122 {"phase": "ecr-repositories", "error": f"{type(exc).__name__}: {exc}"}
123 )
124 try:
125 result["kms"] = _schedule_retained_kms_keys(ctx)
126 except Exception as exc: # preserve partial evidence
127 result["errors"].append({"phase": "kms", "error": f"{type(exc).__name__}: {exc}"})
128 # Account-level state the stacks' deletion never reverts: the enabler's
129 # Delete is a no-op, so the run puts Transaction Search back itself.
130 try:
131 result["transaction_search"] = _restore_transaction_search(ctx)
132 except Exception as exc: # preserve partial evidence
133 if isinstance(exc, TransactionSearchRestoreError):
134 result["transaction_search"] = copy.deepcopy(exc.details)
135 result["errors"].append(
136 {"phase": "transaction-search", "error": f"{type(exc).__name__}: {exc}"}
137 )
138 result["ended_at"] = utc_now()
139 ctx.checkpoint.state.setdefault("retained_cleanup_attempts", []).append(result)
140 ctx.persist()
141 if result["errors"]:
142 raise RuntimeError(
143 "Retained resource cleanup failed: " + json.dumps(result["errors"], sort_keys=True)
144 )
145 return result