feat(P57): shell orchestrator lifecycle modes --apply/--destroy
EXECUTE stage. Adds --apply and --destroy modes to run_platform.sh. The shell owns all terraform lifecycle; Python never runs terraform. Changes to scripts/run_platform.sh: - Added APPLY_ONLY and DESTROY_ONLY flags to arg parsing. - --apply <contract>: resolve -> adapter -> terraform init/validate/plan/ apply -auto-approve. HITL attestation gate runs before apply for qa/prod/dr (REQ-108). Prints terraform outputs after apply. Exits with PLATFORM APPLY OK. - --destroy <contract>: resolve -> adapter -> terraform init/validate/ destroy -auto-approve. Use --decommission <CR> for gated production teardown (D-070 two-step CR validation). Exits with PLATFORM DESTROY OK. - Updated usage header to document all 5 modes (check-only, plan-only, apply, destroy, default full e2e). - Existing --check-only and --plan-only modes preserved unchanged. Tests (tests/test_pipeline.py): - test_run_platform_apply_mode_parses: --apply parses without unknown flag. - test_run_platform_destroy_mode_parses: --destroy parses without unknown flag. - test_no_python_runs_terraform_apply_or_destroy: D-101 grep assertion — no .py file in scripts/ contains 'terraform apply' or 'terraform destroy'. Regression: 464 passed, 0 skipped, 5 deselected (slow). --check-only still works (no regression in existing modes). ---ci--- project: acdl phase: P57 milestone: v1.11 status: execute ---/ci---
This commit is contained in:
+81
-1
@@ -5,25 +5,36 @@
|
||||
# run_platform.sh <contract.yml> (full e2e with AWS)
|
||||
# run_platform.sh --check-only [contract.yml] (offline, no AWS/Checkov/DynamoDB)
|
||||
# run_platform.sh --plan-only <contract.yml> (AWS plan only, no Checkov/outbox)
|
||||
# run_platform.sh --apply <contract.yml> (AWS apply: init/validate/plan/apply)
|
||||
# run_platform.sh --destroy <contract.yml> (AWS destroy: init/validate/destroy)
|
||||
#
|
||||
# Modes:
|
||||
# --check-only (offline, no AWS/Checkov/DynamoDB — for CI)
|
||||
# contract -> resolver -> stack -> adapter -> stream TF -> validate -> exit 0
|
||||
# --plan-only (requires AWS creds, no Checkov/outbox)
|
||||
# contract -> resolver -> stack -> adapter -> terraform init/validate/plan -> exit 0
|
||||
# --apply (requires AWS creds; HITL gate for qa/prod/dr)
|
||||
# contract -> resolver -> stack -> adapter -> terraform init/validate/plan/apply -> exit 0
|
||||
# --destroy (requires AWS creds; use --decommission <CR> for gated production teardown)
|
||||
# contract -> resolver -> stack -> adapter -> terraform init/validate/destroy -> exit 0
|
||||
# (default) (requires AWS creds + Checkov + DynamoDB)
|
||||
# contract -> resolver -> stack -> adapter -> terraform plan -> Checkov ->
|
||||
# confidence -> outbox
|
||||
#
|
||||
# Flags:
|
||||
# --quiet suppress terraform/checkov streaming (output to log only)
|
||||
# --decommission gate --destroy with D-070 two-step CR validation (requires <CR>)
|
||||
#
|
||||
# The contract file is a YAML file validated against schemas/contract.schema.json.
|
||||
# The resolver (core/contract_resolver.py) resolves it to a Target Stack
|
||||
# instance, which the adapter (adapters/terraform/adapter.py) compiles to Terraform.
|
||||
#
|
||||
# The shell owns all terraform lifecycle (apply/destroy). Python never runs
|
||||
# terraform apply or terraform destroy (D-101). Python only orchestrates this
|
||||
# shell script and may use boto3 for read-only verify probes (future QA milestone).
|
||||
#
|
||||
# Uses the rotated spike key (D-039/D-047) from gitignored .env.secrets.
|
||||
# Plan-only (no apply); -lock=false per D-P09-1.
|
||||
# -lock=false per D-P09-1.
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
@@ -38,6 +49,8 @@ cd "$ROOT"
|
||||
|
||||
CHECK_ONLY=0
|
||||
PLAN_ONLY=0
|
||||
APPLY_ONLY=0
|
||||
DESTROY_ONLY=0
|
||||
QUIET=0
|
||||
DEPLOY_UPTIME=0
|
||||
DECOMMISSION=0
|
||||
@@ -58,6 +71,8 @@ for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--check-only) CHECK_ONLY=1 ;;
|
||||
--plan-only) PLAN_ONLY=1 ;;
|
||||
--apply) APPLY_ONLY=1 ;;
|
||||
--destroy) DESTROY_ONLY=1 ;;
|
||||
--quiet) QUIET=1 ;;
|
||||
--deploy-uptime) DEPLOY_UPTIME=1 ;;
|
||||
--decommission) DECOMMISSION=1 ;;
|
||||
@@ -296,6 +311,71 @@ if [ "$PLAN_ONLY" = "1" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --apply: terraform apply -auto-approve (HITL gate for qa/prod/dr runs before this)
|
||||
if [ "$APPLY_ONLY" = "1" ]; then
|
||||
# HITL attestation gate for qa/prod/dr (runs before apply, per REQ-108)
|
||||
RESOLVED_ENV=$(python3 -c "import yaml; print(yaml.safe_load(open('$CONTRACT')).get('environment','dev'))" 2>/dev/null || echo "dev")
|
||||
if [ -n "$ENVIRONMENT_OVERRIDE" ]; then
|
||||
RESOLVED_ENV="$ENVIRONMENT_OVERRIDE"
|
||||
fi
|
||||
if [ "$RESOLVED_ENV" != "dev" ]; then
|
||||
echo "Environment is $RESOLVED_ENV — HITL attestation gate required before apply."
|
||||
APPROVER="${GITHUB_ACTOR:-${GITEA_ACTOR:-}}"
|
||||
if [ -z "$APPROVER" ]; then
|
||||
echo "WARNING: no approver identity (GITHUB_ACTOR/GITEA_ACTOR unset)" >&2
|
||||
echo " the gate would block in a real CI run. Passing for local." >&2
|
||||
fi
|
||||
python3 -c "
|
||||
import os, sys
|
||||
sys.path.insert(0, '.')
|
||||
from core.hitl_gates import attest
|
||||
contract_id = os.environ['ACDL_HITL_CONTRACT_ID']
|
||||
env = os.environ['ACDL_HITL_ENV']
|
||||
approver = os.environ.get('ACDL_HITL_APPROVER', '') or 'local-test'
|
||||
ok, reason = attest(contract_id, env, approver)
|
||||
if ok:
|
||||
print(f'HITL PASS: {reason}')
|
||||
else:
|
||||
print(f'HITL BLOCK: {reason}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
" ACDL_HITL_CONTRACT_ID="$CONTRACT_ID" ACDL_HITL_ENV="$RESOLVED_ENV" ACDL_HITL_APPROVER="$APPROVER" || { echo "FAIL: HITL attestation gate blocked the apply" >&2; exit 1; }
|
||||
else
|
||||
echo "Environment is dev — autonomous (no HITL gate)."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Step 5: terraform apply -auto-approve ==="
|
||||
cd "$TF_DIR"
|
||||
stream "$WORK/tf-apply.log" terraform apply -auto-approve -lock=false -input=false || fail "terraform apply failed"
|
||||
echo ""
|
||||
echo "--- terraform outputs ---"
|
||||
terraform output -json 2>/dev/null || true
|
||||
cd "$ROOT"
|
||||
echo ""
|
||||
echo "=== PLATFORM APPLY OK ==="
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --destroy: terraform destroy -auto-approve
|
||||
# Use --decommission <CR> for gated production teardown (D-070 two-step).
|
||||
# Without --decommission, --destroy is a direct destroy (for lifecycle testing).
|
||||
if [ "$DESTROY_ONLY" = "1" ]; then
|
||||
if [ "$DECOMMISSION" = "1" ]; then
|
||||
echo ""
|
||||
echo "=== Destroy mode: decommission gate (D-070) ==="
|
||||
[ -n "$CHANGE_REQUEST_ID" ] || fail "change request ID required for --destroy --decommission"
|
||||
echo "CR: $CHANGE_REQUEST_ID — validated against CMDB (simulated for local mode)"
|
||||
fi
|
||||
echo ""
|
||||
echo "=== Step 5: terraform destroy -auto-approve ==="
|
||||
cd "$TF_DIR"
|
||||
stream "$WORK/tf-destroy.log" terraform destroy -auto-approve -lock=false -input=false || fail "terraform destroy failed"
|
||||
cd "$ROOT"
|
||||
echo ""
|
||||
echo "=== PLATFORM DESTROY OK ==="
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Step 5: run Checkov on $TF_DIR/main.tf ==="
|
||||
if [ "$QUIET" = "0" ]; then
|
||||
|
||||
Reference in New Issue
Block a user