diff --git a/scripts/ops/prune_cf_e2e_dns.sh b/scripts/ops/prune_cf_e2e_dns.sh index 309387c64..36f2a83fa 100755 --- a/scripts/ops/prune_cf_e2e_dns.sh +++ b/scripts/ops/prune_cf_e2e_dns.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # prune_cf_e2e_dns.sh — targeted, fail-closed cleanup of disposable E2E DNS -# records that accumulate under the moleculesai.app zone and exhaust the -# Cloudflare DNS record quota (code 81045). +# records that accumulate under the moleculesai.app zone (typically the +# staging.moleculesai.app subdomain) and exhaust the Cloudflare DNS record +# quota (code 81045). # # Why this exists: staging E2E harnesses create DNS records for slugs like # e2e-smoke--- and e2e-tmpl- (see @@ -16,6 +17,8 @@ # - Records whose full name matches # e2e-smoke-*. # e2e-tmpl-*. +# - Multiple zone domains may be supplied as a comma-separated list +# (e.g. "moleculesai.app,staging.moleculesai.app"). # - Records older than --min-age-hours / PRUNE_MIN_AGE_HOURS (default 24) # so in-flight runs are not touched. # - Anything else is kept untouched. @@ -33,7 +36,8 @@ # PRUNE_MIN_AGE_HOURS= — default minimum age in hours (default: 24). # MAX_DELETE_PCT= — refuse to delete more than this percentage of # matched ephemeral records (default: 50). -# PRUNE_ZONE_DOMAIN= — zone domain to anchor matches (default: moleculesai.app). +# PRUNE_ZONE_DOMAIN= — comma-separated zone domain(s) to anchor +# matches (default: staging.moleculesai.app). # # Exit codes: # 0 — dry-run completed or prune executed successfully @@ -45,7 +49,10 @@ set -euo pipefail DRY_RUN=1 MIN_AGE_HOURS="${PRUNE_MIN_AGE_HOURS:-24}" MAX_DELETE_PCT="${MAX_DELETE_PCT:-50}" -ZONE_DOMAIN="${PRUNE_ZONE_DOMAIN:-moleculesai.app}" +ZONE_DOMAIN="${PRUNE_ZONE_DOMAIN:-}" +if [ -z "$ZONE_DOMAIN" ]; then + ZONE_DOMAIN="staging.moleculesai.app" +fi while [ $# -gt 0 ]; do case "$1" in @@ -246,15 +253,19 @@ from datetime import datetime, timezone, timedelta min_age = timedelta(hours=int(os.environ["MIN_AGE_HOURS"])) zone_domain = os.environ["ZONE_DOMAIN"] +zone_domains = [d.strip() for d in zone_domain.split(",") if d.strip()] now = datetime.now(timezone.utc) -# Conservative: only the two known disposable E2E prefixes, anchored to the -# configured zone domain so similarly-named records in other zones never match. +# Conservative: only the two known disposable E2E prefixes, anchored to one +# of the configured zone domains so similarly-named records in other zones +# never match. Multiple zone domains may be supplied as a comma-separated +# list (e.g. "moleculesai.app,staging.moleculesai.app"). # The prefix MUST be followed immediately by a hyphen and then at least one # suffix character, so names like e2e-smokeprod, e2e-smoketest-keep, or # prod-e2e-smoke-x are NEVER matched. +zone_part = r"(?:" + r"|".join(re.escape(d) for d in zone_domains) + r")" EPHEMERAL_RE = re.compile( - r"^(e2e-smoke-[a-zA-Z0-9_-]+|e2e-tmpl-[a-zA-Z0-9_-]+)\." + re.escape(zone_domain) + r"$" + r"^(e2e-smoke-[a-zA-Z0-9_-]+|e2e-tmpl-[a-zA-Z0-9_-]+)\." + zone_part + r"$" ) def parse_iso(s): diff --git a/tests/ops/test_prune_cf_e2e_dns_fail_closed.sh b/tests/ops/test_prune_cf_e2e_dns_fail_closed.sh index 8d7d9b2e6..6eb284361 100755 --- a/tests/ops/test_prune_cf_e2e_dns_fail_closed.sh +++ b/tests/ops/test_prune_cf_e2e_dns_fail_closed.sh @@ -17,7 +17,7 @@ PASS=0 FAIL=0 run_case() { - local name="$1" list_exit="$2" list_body="$3" expect_delete_sentinel="$4" zone_domain="${5:-moleculesai.app}" + local name="$1" list_exit="$2" list_body="$3" expect_delete_sentinel="$4" zone_domain="${5:-}" local tmp tmp=$(mktemp -d -t cf-e2e-prune-fail-closed-XXXXXX) local delete_sentinel="$tmp/delete_reached" @@ -129,9 +129,10 @@ run_case "CF DNS list returns malformed JSON" 0 'this is not json' run_case "CF DNS list returns non-array result" 0 '{"success":true,"result":{"id":"rec1"}}' false-abort # Helper to build a DNS list result with one record, given created_on ISO string -# and optional zone domain (default: moleculesai.app). +# and optional zone domain (default: staging.moleculesai.app, the observed +# domain for leaked e2e-smoke/e2e-tmpl records). make_list() { - local created_on="$1" zone_domain="${2:-moleculesai.app}" + local created_on="$1" zone_domain="${2:-staging.moleculesai.app}" cat <