Files
acdl/scripts/run_codegen.sh
T
Jon Chery 85c500e45a
Nova Slides Render / render (push) Failing after 1m1s
feat(P4): pipeline hardening — Checkov before plan, Wiz-or-Checkov on plan (REQ-250)
Two-stage policy scan per item 20:

1. Checkov on static code BEFORE terraform plan (fail-fast, quick dev
   feedback). Added to run_platform.sh Step 3c + run_codegen.sh Step 3c
   (runs on the authored TF dir before plan, using --framework terraform).

2. Runtime policy scan on the plan AFTER terraform plan: Wiz when
   configured (WIZ_API_TOKEN + WIZ_API_URL), else Checkov against the
   plan as a drop-in replacement (--framework terraform_plan). Wiz and
   Checkov are NEVER both run on the plan. Replaces the old single
   Checkov-on-main.tf step in run_platform.sh Step 5 + run_postapply.sh
   Step 5.

pipelines/contract.yml: stage list updated — 'checkov' stage replaced by
'checkov-static' (before terraform-plan) + 'runtime-policy-scan' (after
terraform-plan). 9 stages → 10 stages. Header comment updated.

adapters/wiz/wiz_adapter.py: add --plan mode CLI (fetch_and_adapt_plan)
for scanning a terraform plan; backward-compat with the positional
<wiz_issues.json> <contract-id> mode. is_configured() gates the Wiz path.

Tests: test_pipeline_contract.py (9 → 10 stages, new stage names);
test_contract_resolver.py (rename test, assert checkov-static +
runtime-policy-scan present, old 'checkov' gone). Full suite: 685 pass
+ 1 pre-existing attestation failure (NOVA_ATTESTATION_SIGNING_KEY_ID
unset, unrelated to v1.21, fails on main without these changes too).

---ci---
project: acdl
phase: 4
milestone: v1.21
status: execute
phase_role: execution
---/ci---
2026-08-11 14:10:42 +00:00

162 lines
6.0 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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')
"
echo "=== Step 3c: Checkov on static code (fail-fast, before terraform plan) ==="
# REQ-250 (v1.21): Checkov runs on the authored Terraform code BEFORE
# terraform plan so developers get immediate policy feedback, not a
# delayed plan-stage failure. The runtime plan scan (Wiz-or-Checkov)
# runs after the plan in run_postapply.sh Step 5.
if [ "$QUIET" = "0" ]; then
checkov -d "$TF_DIR" --framework terraform -o json --soft-fail --external-checks-dir adapters/terraform/policy/custom_rules/ 2>&1 | tee "$WORK/checkov-static.json"
else
checkov -d "$TF_DIR" --framework terraform -o json --soft-fail --external-checks-dir adapters/terraform/policy/custom_rules/ > "$WORK/checkov-static.json" 2> "$WORK/checkov-static.err"
fi
[ -s "$WORK/checkov-static.json" ] || { echo "FAIL: checkov (static) produced no output" >&2; exit 1; }
echo ""
echo "checkov (static) summary: $(python3 -c "import json; d=json.load(open('$WORK/checkov-static.json')); print(len(d.get('results',{}).get('failed_checks',[])), 'failed,', len(d.get('results',{}).get('passed_checks',[])), 'passed')")"
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"