feat(P4): transparent terraform + feature flags + run_platform.sh split (REQ-233..238)

Create run_codegen.sh (pre-TF: env check, validate, resolve, adapt).
Create run_postapply.sh (post-TF: Checkov, confidence, HITL, outbox, SSM, uptime).
Add variable 'enabled' (bool, default true) + count=var.enabled?1:0 to all 12
L1 modules (alb, cloudfront, ecr, ecs-cluster, ecs-service, iam-role, kms-key,
rds, s3, uptime, vpc, waf). Fix all cross-resource references with [0] indexing.
Update interface.json for all modules to declare 'enabled' input.
Fix stale artifact path /tmp/acdl_platform_run_v18 → /tmp/nova_platform_run (REQ-238).
run_platform.sh remains as backward-compat shim for local-dev usage.

---ci---
project: acdl
phase: 4
milestone: v1.20
status: execute
requirements: [REQ-233, REQ-234, REQ-235, REQ-236, REQ-237, REQ-238]
---/ci---
This commit is contained in:
Jon Chery
2026-08-07 18:49:56 +00:00
parent ed5ea90654
commit 0ca383dae6
52 changed files with 667 additions and 79 deletions
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env bash
# scripts/run_codegen.sh — pre-Terraform codegen for the Nova platform pipeline.
#
# Performs steps 03b of run_platform.sh:
# 0. Environment onboarding check
# 1. Validate contract against contract.schema.json
# 2. Resolve contract → Target Stack instance (contract_resolver.py)
# 3. Adapter compiles stack → Terraform (adapter.py)
# 3b. Structural validation of emitted TF (offline)
#
# Emits Terraform files to $NOVA_WORK_DIR/tf/{main.tf,terraform.tf,providers.tf}
# and prints the work dir path for the caller (workflow or run_platform.sh)
# to use for native terraform init/validate/plan/apply steps.
#
# Usage:
# run_codegen.sh <contract.yml> [--environment <name>] [--check-only]
#
# When --check-only is passed, exits 0 after structural validation (no AWS).
# Otherwise, loads AWS credentials from .env.secrets if not already set,
# and exits 0 with the work dir ready for terraform.
#
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CALLER_CWD="$(pwd)"
cd "$ROOT"
CHECK_ONLY=0
ENVIRONMENT_OVERRIDE=""
CONTRACT=""
while [ $# -gt 0 ]; do
case "$1" in
--check-only) CHECK_ONLY=1; shift ;;
--environment) shift; ENVIRONMENT_OVERRIDE="$1"; shift ;;
--environment=*) ENVIRONMENT_OVERRIDE="${1#--environment=}"; shift ;;
-h|--help)
echo "Usage: run_codegen.sh <contract.yml> [--environment <name>] [--check-only]"
exit 0 ;;
*) CONTRACT="$1"; shift ;;
esac
done
[ -n "$CONTRACT" ] || { echo "FAIL: no contract file specified" >&2; exit 1; }
# Resolve relative contract path against caller's CWD
if ! [[ "$CONTRACT" = /* ]]; then
CONTRACT="$CALLER_CWD/$CONTRACT"
fi
[ -f "$CONTRACT" ] || { echo "FAIL: contract not found: $CONTRACT" >&2; exit 1; }
if [ -n "$ENVIRONMENT_OVERRIDE" ]; then
export NOVA_ENVIRONMENT_OVERRIDE="$ENVIRONMENT_OVERRIDE"
fi
CONTRACT_ID="${NOVA_CONTRACT_ID:-11111111-1111-1111-1111-111111111111}"
WORK="${NOVA_WORK_DIR:-/tmp/nova_platform_run}"
TF_DIR="$WORK/tf"
rm -rf "$WORK"; mkdir -p "$TF_DIR"
echo "=== Step 0: environment onboarding check ==="
if [ -n "$ENVIRONMENT_OVERRIDE" ]; then
python3 core/environment_check.py --env="$ENVIRONMENT_OVERRIDE" || {
echo "FAIL: environment onboarding check failed for $ENVIRONMENT_OVERRIDE" >&2
exit 1
}
else
python3 core/environment_check.py || true
fi
echo ""
echo "=== Step 1: validate contract against contract.schema.json ==="
python3 -c "
import json, sys, yaml
from jsonschema import validate
schema = json.load(open('schemas/contract.schema.json'))
doc = yaml.safe_load(open('$CONTRACT'))
validate(instance=doc, schema=schema)
print(f'contract valid: {doc.get(\"name\", \"unnamed\")} (env={doc.get(\"environment\",\"dev\")})')
"
echo ""
echo "=== Step 2: resolve contract → Target Stack instance ==="
python3 -c "
import json, sys
sys.path.insert(0, '$ROOT')
from core.contract_resolver import resolve
stack = resolve('$CONTRACT', environment_override='${ENVIRONMENT_OVERRIDE}' or None)
json.dump(stack, open('$WORK/stack.json', 'w'), indent=2)
print(f'stack resolved: {stack[\"stack\"][\"name\"]} ({len(stack[\"resources\"])} resource(s))')
"
echo ""
echo "=== Step 3: adapter compiles stack → $TF_DIR/*.tf ==="
python3 -c "
import json, sys
sys.path.insert(0, '$ROOT')
from adapters.terraform.adapter import TerraformAdapter
stack = json.load(open('$WORK/stack.json'))
adapter = TerraformAdapter()
adapter.compile(stack, '$TF_DIR')
print('adapter: main.tf + terraform.tf + providers.tf written')
"
if [ "$CHECK_ONLY" = "1" ]; then
echo ""
echo "=== Step 3b: validate adapter output structure (offline) ==="
python3 -c "
import json, os
d = json.load(open('$WORK/stack.json'))
assert d['stack']['name'], 'stack name missing'
assert len(d['resources']) >= 1, 'expected at least 1 resource'
tf_dir = '$TF_DIR'
for f in ('main.tf', 'terraform.tf', 'providers.tf'):
assert os.path.isfile(os.path.join(tf_dir, f)), f'{f} missing'
main = open(os.path.join(tf_dir, 'main.tf')).read()
assert len(main) > 0, 'main.tf is empty'
tf = open(os.path.join(tf_dir, 'terraform.tf')).read()
assert 'backend' in tf
assert 'required_version' in tf
prov = open(os.path.join(tf_dir, 'providers.tf')).read()
assert 'provider \"aws\"' in prov
print(f\"adapter output: OK ({d['stack']['name']}, {len(d['resources'])} resource(s))\")
"
echo ""
echo "=== CODEGEN CHECK OK ==="
echo "contract → resolver → stack → adapter → structure validated (offline, no AWS)"
echo "WORK_DIR=$WORK"
exit 0
fi
# Load AWS credentials if not already set (for non-check-only modes)
if [ -z "${AWS_ACCESS_KEY_ID:-}" ] || [ -z "${AWS_SECRET_ACCESS_KEY:-}" ]; then
ENV_FILE="$ROOT/.env.secrets"
if [ -f "$ENV_FILE" ]; then
set -a
. "$ENV_FILE"
set +a
export AWS_ACCESS_KEY_ID="$NOVA_AWS_ACCESS_KEY_ID"
export AWS_SECRET_ACCESS_KEY="$NOVA_AWS_SECRET_ACCESS_KEY"
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
fi
fi
echo ""
echo "=== CODEGEN OK ==="
echo "Terraform files ready in: $TF_DIR"
echo "WORK_DIR=$WORK"