Coverage for scripts / live_release_validation / ownership / vpc_endpoints.py: 100.00%
42 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-14 22:07 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-14 22:07 +0000
1"""Accepted-residue accounting for VPC endpoints EC2 has already deleted.
3The Resource Groups Tagging API is an index, not the source of truth, and it
4lags resource deletion by minutes. Gateway VPC endpoints (the S3 and DynamoDB
5endpoints the regional stack creates) are deleted synchronously with their
6stack, yet the index kept returning both ARNs — still carrying their
7``aws:cloudformation:stack-id`` tags — when ``final-inventory`` ran seconds
8after the stack was gone. Observed live on 2026-09-12: two
9``vpc-endpoint/vpce-…`` ARNs failed the all-zero teardown gate while
10``DescribeVpcEndpoints`` already answered ``InvalidVpcEndpointId.NotFound`` for
11both. The same stale entries would fail the next run's clean-account gate.
13``_strip_deleted_vpc_endpoints`` accepts exactly that shape and nothing else: a
14``tagged_resources`` entry whose ARN parses as a VPC endpoint in the same
15region and expected account is stripped **only after** EC2 itself confirms the
16endpoint does not exist or is in a terminal ``deleting``/``deleted`` state. A
17live endpoint keeps its entry as genuine residue. Every acceptance is returned
18as evidence (region, endpoint id, the EC2 check performed, tags) so ``baseline``
19and ``final-inventory`` disclose what they tolerated rather than silently
20ignoring it — the same posture as ``ownership/dynamodb_streams.py``.
21"""
23from __future__ import annotations
25import copy
26import re
27from typing import Any
29from botocore.exceptions import ClientError
31from ..models import RunContext
33_VPC_ENDPOINT_ARN = re.compile(
34 r"^arn:[^:]+:ec2:(?P<region>[a-z0-9-]+):(?P<account>\d{12})"
35 r":vpc-endpoint/(?P<endpoint_id>vpce-[0-9a-f]+)$"
36)
37#: EC2 states in which the endpoint is already being removed and cannot be
38#: residue; anything else (``available``, ``pending``, ``failed``, ...) is.
39_TERMINAL_ENDPOINT_STATES = frozenset({"deleting", "deleted"})
40_NOT_FOUND_CODES = frozenset({"InvalidVpcEndpointId.NotFound", "InvalidVpcEndpoint.NotFound"})
43def _endpoint_state(ctx: RunContext, region: str, endpoint_id: str) -> str:
44 """Return the live endpoint state, or ``ABSENT`` once EC2 no longer knows it."""
45 ec2 = ctx.session.client("ec2", region_name=region)
46 try:
47 response = ec2.describe_vpc_endpoints(VpcEndpointIds=[endpoint_id])
48 except ClientError as exc:
49 if exc.response.get("Error", {}).get("Code") not in _NOT_FOUND_CODES:
50 raise
51 return "ABSENT"
52 endpoints = response.get("VpcEndpoints") or []
53 if not endpoints:
54 return "ABSENT"
55 return str(endpoints[0].get("State") or "UNKNOWN")
58def _strip_deleted_vpc_endpoints(
59 ctx: RunContext,
60 project_inventory: dict[str, Any],
61) -> tuple[dict[str, Any], list[dict[str, Any]]]:
62 """Strip tagged VPC endpoint ARNs whose endpoint EC2 proves gone."""
63 inventory = copy.deepcopy(project_inventory)
64 accepted: list[dict[str, Any]] = []
65 for region, resources in list(inventory.get("regional", {}).items()):
66 kept: list[dict[str, Any]] = []
67 for entry in resources.get("tagged_resources", []):
68 arn = str(entry.get("arn") or "")
69 match = _VPC_ENDPOINT_ARN.match(arn)
70 if (
71 match is None
72 or match.group("region") != region
73 or match.group("account") != ctx.settings.expected_account
74 ):
75 kept.append(entry)
76 continue
77 endpoint_id = match.group("endpoint_id")
78 state = _endpoint_state(ctx, region, endpoint_id)
79 if state != "ABSENT" and state not in _TERMINAL_ENDPOINT_STATES:
80 # EC2 still has it: genuine residue, keep it visible.
81 kept.append(entry)
82 continue
83 accepted.append(
84 {
85 "region": region,
86 "arn": arn,
87 "endpoint_id": endpoint_id,
88 "endpoint_state": state,
89 "authority": "ec2:DescribeVpcEndpoints",
90 "tags": dict(entry.get("tags") or {}),
91 "note": (
92 "the Resource Groups Tagging API index lags endpoint deletion; "
93 "EC2 is the authority for existence"
94 ),
95 }
96 )
97 resources["tagged_resources"] = kept
98 if not any(resources.values()):
99 inventory["regional"].pop(region)
100 return inventory, accepted