diff --git a/scripts/dc-node-v6-carve.py b/scripts/dc-node-v6-carve.py index 0b217e9..b5d50f0 100644 --- a/scripts/dc-node-v6-carve.py +++ b/scripts/dc-node-v6-carve.py @@ -33,10 +33,20 @@ """ import argparse import json +import os import subprocess import sys -PROFILE_DEFAULT = "admin" +# PROFILE RESOLUTION -- reads MAAS_PROFILE, 2026-07-30. +# This script was ENV-BLIND: the default was the literal "admin" and the only +# way to change it was --profile, with no environment read anywhere. Every bash +# tool in this repo honours MAAS_PROFILE, so `export MAAS_PROFILE=vr1-dc0-region` +# -- the idiom a session naturally uses -- left THIS tool silently targeting the +# OFFICE1 region. During the D-132 q1 per-DC region migration that is the +# unrecoverable direction: against the wrong region a carve is an idempotent +# no-op that prints PASS while the region you meant stays untouched. +# An explicit --profile still wins over the environment. +PROFILE_DEFAULT = os.environ.get("MAAS_PROFILE", "admin") def die(msg, rc=2): @@ -46,7 +56,14 @@ def maas(profile, *args): - p = subprocess.run(["maas", profile, *args], capture_output=True, text=True) + # An ABSENT maas CLI used to raise FileNotFoundError and exit with a + # traceback (exit 1), which is indistinguishable from a real failure and is + # emphatically not this script's "REFUSE = could not evaluate" contract. + # "Could not look" is never "nothing there". + try: + p = subprocess.run(["maas", profile, *args], capture_output=True, text=True) + except FileNotFoundError: + die("no 'maas' CLI on this host -- cannot evaluate the v6 carve") if p.returncode != 0: return None, (p.stderr or p.stdout).strip()[:300] return p.stdout, None @@ -70,6 +87,12 @@ ap.add_argument("--profile", default=PROFILE_DEFAULT) a = ap.parse_args() + # Say WHICH region this run targets, and where that came from. A capture that + # does not name the profile cannot be audited for wrong-region work. + src = "--profile" if any(x == "--profile" for x in sys.argv) else ( + "MAAS_PROFILE env" if "MAAS_PROFILE" in os.environ else "built-in default") + print(f"dc-node-v6-carve {a.action} {a.site}: maas profile '{a.profile}' (from {src})") + subs, err = maas_json(a.profile, "subnets", "read") if subs is None: die(f"'maas {a.profile} subnets read' failed -- {err}. NOTE an ABSENT `maas` binary " diff --git a/tests/dc-node-v6-carve/run-tests.sh b/tests/dc-node-v6-carve/run-tests.sh index 48d0a58..39b4ca3 100755 --- a/tests/dc-node-v6-carve/run-tests.sh +++ b/tests/dc-node-v6-carve/run-tests.sh @@ -103,6 +103,33 @@ D="$TMP/dry"; mkbin "$D" full none ok run 0 'DRY RUN -- nothing was written' "T9 apply is DRY by default" "$D" apply vr1-dc0 +# --- PROFILE RESOLUTION (2026-07-30) --------------------------------------- +# This script was ENV-BLIND: default "admin", --profile only, no environment +# read. Every bash tool here honours MAAS_PROFILE, so `export MAAS_PROFILE=...` +# left THIS tool silently targeting OFFICE1 -- the unrecoverable direction +# during the D-132 q1 per-DC region migration. These assertions pin all three +# resolution paths AND require the run to SAY which region it targets, because a +# capture that does not name the profile cannot be audited for wrong-region work. +pchk(){ # pchk [extra args...] + local desc="$1" want="$2" envv="$3"; shift 3 + local out + if [ -n "$envv" ]; then out="$(MAAS_PROFILE="$envv" python3 "$REPO/scripts/dc-node-v6-carve.py" check vr1-dc0 "$@" 2>&1)" + else out="$(env -u MAAS_PROFILE python3 "$REPO/scripts/dc-node-v6-carve.py" check vr1-dc0 "$@" 2>&1)"; fi + case "$out" in *"$want"*) echo " PASS $desc"; PASS=$((PASS+1)) ;; + *) echo " FAIL $desc (wanted '$want' in: $(printf '%s' "$out" | head -1))"; FAIL=$((FAIL+1)) ;; esac +} +pchk "T10 MAAS_PROFILE env is honoured" "profile 'env-region' (from MAAS_PROFILE env)" "env-region" +pchk "T11 --profile overrides the environment" "profile 'flag-region' (from --profile)" "env-region" --profile flag-region +pchk "T12 no env and no flag falls back to admin" "profile 'admin' (from built-in default)" "" + +# An ABSENT maas CLI must REFUSE (exit 2), not traceback (exit 1). A traceback is +# indistinguishable from a real failure and breaks the REFUSE contract. +OUT="$(PATH=/nonexistent-for-test:/usr/bin:/bin python3 "$REPO/scripts/dc-node-v6-carve.py" check vr1-dc0 2>&1)"; RC=$? +if [ "$RC" = 2 ]; then echo " PASS T13 absent maas CLI REFUSES with exit 2"; PASS=$((PASS+1)); +else echo " FAIL T13 absent maas CLI REFUSES with exit 2 (got $RC)"; FAIL=$((FAIL+1)); fi +case "$OUT" in *Traceback*) echo " FAIL T14 absent maas CLI does not traceback"; FAIL=$((FAIL+1)) ;; + *) echo " PASS T14 absent maas CLI does not traceback"; PASS=$((PASS+1)) ;; esac + echo echo "RESULT: PASS=$PASS FAIL=$FAIL" [ "$FAIL" -eq 0 ] || exit 1