diff --git a/docs/v1-redeploy-changelog.md b/docs/v1-redeploy-changelog.md index be46b22..bee2c69 100644 --- a/docs/v1-redeploy-changelog.md +++ b/docs/v1-redeploy-changelog.md @@ -1558,3 +1558,25 @@ --remote-ip 0.0.0.0/0 ); or git checkout both files at this commit's parent. Next-free: D-071, DOCFIX-085, BUNDLEFIX-009. + +### 2026-07-03 (addendum 6) -- DOCFIX-085: d063-apply.sh structured-capture fix (live audit fail-closed) + +First live run of d063-apply.sh --audit failed CLOSED at exit 20 with zero mutations (SG and +project resolution were clean). Measured mechanism (streams separated in a follow-up probe): +`--long` is deprecated in the deployed openstackclient and prints its warning ON STDERR; the +script captured JSON with 2>&1, so the warning landed at char 0 and killed the parse. + +DOCFIX-085 -- scripts/d063-apply.sh + tests/d063-apply/. + WHAT: drop --long everywhere (deprecated; fields present without it); NEVER merge stderr + into structured-output captures -- both JSON rule-list captures now write stderr to a + tempfile, surfaced as informational on success and printed in full diagnostics (stdout head + + stderr head) on parse failure, fixing the diagnostic gap where PARSE-ERROR swallowed the + evidence. WHY: any stderr warning (deprecations today, SDK notices tomorrow) must never be + able to corrupt a parse; silencing with 2>/dev/null was rejected as error-masking. + ACCEPT: tests/d063-apply 7/7 with the mock made FAITHFUL to the real CLI (rule-list now + always warns on stderr), so every scenario regression-tests the stream separation. + CONVENTION (add to script-authoring): structured-output (-f json/value) captures take + stdout ONLY; stderr goes to a tempfile and is reported, never merged, never discarded. + REVERT: git checkout both files at this commit's parent. + +Next-free: D-071, DOCFIX-086, BUNDLEFIX-009. diff --git a/scripts/d063-apply.sh b/scripts/d063-apply.sh index a6e0824..93f4fbd 100644 --- a/scripts/d063-apply.sh +++ b/scripts/d063-apply.sh @@ -23,6 +23,7 @@ else printf '%s\n' "$out" | sed 's/^/ /'; echo " ^ FAILED: $*"; eval "$_c=\$(( $_c + 1 ))"; fi return 0; } is_id(){ [[ "${1:-}" =~ ^[0-9a-f]{32}$ ]]; } +ERRF=$(mktemp); trap 'rm -f "$ERRF"' EXIT is_v4(){ [[ "${1:-}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; } for v in $(env | awk -F= '/^OS_/{print $1}'); do unset "$v"; done source "$HOME/admin-openrc" @@ -36,7 +37,9 @@ echo " project=$PID sg=$SGID" echo "=== current ingress rules (tcp 22/6443) ===" -RULES=$(openstack security group rule list "$SGID" --long -f json &1) || true +# DOCFIX-085: no --long (deprecated; warns on stderr) and NEVER merge stderr into a +# structured-output capture -- any warning lands at char 0 and kills the parse. +RULES=$(openstack security group rule list "$SGID" -f json "$ERRF") || true WIDE=$(printf '%s\n' "$RULES" | python3 -c " import sys,json try: @@ -47,7 +50,8 @@ print(r['ID'], pr, rip or '') except Exception as e: print('PARSE-ERROR', e)") printf '%s\n' "${WIDE:-}" | sed 's/^/ /' -grep -q 'PARSE-ERROR' <<<"$WIDE" && { echo "PRECOND: rule list unparsed"; exit 20; } +grep -q 'PARSE-ERROR' <<<"$WIDE" && { echo "PRECOND: rule list unparsed"; echo " stdout head:"; printf '%s\n' "$RULES" | head -3 | sed 's/^/ /'; echo " stderr:"; head -3 "$ERRF" | sed 's/^/ /'; exit 20; } +[ -s "$ERRF" ] && { echo " (stderr from rule list, informational):"; head -2 "$ERRF" | sed 's/^/ /'; } WIDE_IDS=$(printf '%s\n' "$WIDE" | awk '$3=="0.0.0.0/0" || $3=="::/0" || $3=="" {print $1}') echo "=== derive magnum unit sources LIVE (ip route get $FIP per unit) ===" @@ -90,7 +94,7 @@ echo " add $PORT<-$SRC: created" done echo "=== phase 1 gate: readback (every planned rule must exist before removes) ===" -RB=$(openstack security group rule list "$SGID" --long -f json &1) || true +RB=$(openstack security group rule list "$SGID" -f json "$ERRF") || true MISS=$(printf '%s\n' "$RB" | python3 -c " import sys,json want=set() @@ -109,6 +113,6 @@ for I in $WIDE_IDS; do echo " remove $I"; run FAILS openstack security group rule delete "$I"; done [ "$FAILS" = 0 ] || { echo "DONE WITH $FAILS REMOVE FAILURES -- wide rules may remain; review"; exit 26; } echo "=== final state ===" -openstack security group rule list "$SGID" --long -f value -c ID -c "IP Protocol" -c "Port Range" -c "IP Range" &1 | sed 's/^/ /' +openstack security group rule list "$SGID" -f value -c ID -c "IP Protocol" -c "Port Range" -c "IP Range" &1 | sed 's/^/ /' echo "D-063 APPLY COMPLETE" exit 0 diff --git a/tests/d063-apply/run-tests.sh b/tests/d063-apply/run-tests.sh index 08f4b6e..1254fca 100644 --- a/tests/d063-apply/run-tests.sh +++ b/tests/d063-apply/run-tests.sh @@ -45,6 +45,7 @@ "security group list"*) if [ "$S" = missingsg ]; then :; else printf '%s capi-mgmt-sg\n' "$(python3 -c "import uuid;print(uuid.UUID(bytes=b's'*16))")"; fi;; "security group rule list"*) + echo "The --long option has been deprecated and is no longer needed" >&2 # faithful: real CLI warns on stderr python3 - "$ST" <<'PY' import json,sys,os,glob st=sys.argv[1]; scen=os.environ.get("MOCK_SCEN","happy")