Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 518bbe32a7 | |||
| ed36519223 |
@@ -183,9 +183,17 @@ python3 -m pytest tests/ -v
|
|||||||
bash scripts/run_platform.sh --check-only
|
bash scripts/run_platform.sh --check-only
|
||||||
# Expected: "=== PLATFORM CHECK OK ==="
|
# Expected: "=== PLATFORM CHECK OK ==="
|
||||||
|
|
||||||
|
# Run the headline E2E against the local emulating tier (no AWS credentials
|
||||||
|
# needed — emulates ECS, outbox, S3 state, Lambda in-process; D-092).
|
||||||
|
bash scripts/run_platform.sh --local
|
||||||
|
# Expected: "=== LOCAL E2E OK ==="
|
||||||
|
|
||||||
# Reproduce the full CI pipeline locally (lint -> test -> check-only)
|
# Reproduce the full CI pipeline locally (lint -> test -> check-only)
|
||||||
bash scripts/run_ci.sh
|
bash scripts/run_ci.sh
|
||||||
# Expected: "=== CI PIPELINE OK ==="
|
# Expected: "=== CI PIPELINE OK ==="
|
||||||
|
|
||||||
|
# Show all run_platform.sh flags + a one-line description each.
|
||||||
|
bash scripts/run_platform.sh --help
|
||||||
```
|
```
|
||||||
|
|
||||||
### CI/CD pipelines
|
### CI/CD pipelines
|
||||||
|
|||||||
@@ -64,6 +64,21 @@ def _load_json(path):
|
|||||||
return json.load(fh)
|
return json.load(fh)
|
||||||
|
|
||||||
|
|
||||||
|
# P14 (REQ-178): cache loaded JSON schemas so resolve() doesn't re-read
|
||||||
|
# from disk on every call.
|
||||||
|
_SCHEMA_CACHE: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _load_schema(path):
|
||||||
|
"""Load a JSON schema with caching (P14, REQ-178)."""
|
||||||
|
cached = _SCHEMA_CACHE.get(path)
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
schema = _load_json(path)
|
||||||
|
_SCHEMA_CACHE[path] = schema
|
||||||
|
return schema
|
||||||
|
|
||||||
|
|
||||||
def _load_yaml(path):
|
def _load_yaml(path):
|
||||||
with open(path, "r") as fh:
|
with open(path, "r") as fh:
|
||||||
return yaml.safe_load(fh)
|
return yaml.safe_load(fh)
|
||||||
@@ -468,7 +483,7 @@ def resolve(contract_path, repo_root=None, environment_override=None):
|
|||||||
contract["environment"] = environment_override
|
contract["environment"] = environment_override
|
||||||
|
|
||||||
# Load schemas
|
# Load schemas
|
||||||
contract_schema = _load_json(os.path.join(repo_root, "schemas", "contract.schema.json"))
|
contract_schema = _load_schema(os.path.join(repo_root, "schemas", "contract.schema.json"))
|
||||||
|
|
||||||
# Validate contract against schema
|
# Validate contract against schema
|
||||||
jsonschema.validate(contract, contract_schema)
|
jsonschema.validate(contract, contract_schema)
|
||||||
@@ -588,7 +603,7 @@ def resolve(contract_path, repo_root=None, environment_override=None):
|
|||||||
stack_instance["outputs"] = merged_outputs
|
stack_instance["outputs"] = merged_outputs
|
||||||
|
|
||||||
# Validate against stack schema
|
# Validate against stack schema
|
||||||
stack_schema = _load_json(os.path.join(repo_root, "schemas", "stack.schema.json"))
|
stack_schema = _load_schema(os.path.join(repo_root, "schemas", "stack.schema.json"))
|
||||||
jsonschema.validate(stack_instance, stack_schema)
|
jsonschema.validate(stack_instance, stack_schema)
|
||||||
|
|
||||||
return stack_instance
|
return stack_instance
|
||||||
|
|||||||
@@ -38,8 +38,10 @@ from core import env as _envhelper
|
|||||||
SSM_PREFIX = "/nova"
|
SSM_PREFIX = "/nova"
|
||||||
KMS_KEY_ID_ENV = "NOVA_KMS_KEY_ID"
|
KMS_KEY_ID_ENV = "NOVA_KMS_KEY_ID"
|
||||||
|
|
||||||
# Outputs that are safe to display in a PR comment (no secrets).
|
# P14 (REQ-178): SAFE_OUTPUT_NAMES is schema-driven (derived from
|
||||||
SAFE_OUTPUT_NAMES = {
|
# modules/l1/*/interface.json outputs that don't have sensitive:true).
|
||||||
|
# Falls back to the hardcoded set if the interfaces can't be read.
|
||||||
|
_HARDCODED_SAFE_OUTPUTS = {
|
||||||
"distribution_domain_name",
|
"distribution_domain_name",
|
||||||
"bucket_arn",
|
"bucket_arn",
|
||||||
"bucket_name",
|
"bucket_name",
|
||||||
@@ -59,6 +61,37 @@ SAFE_OUTPUT_NAMES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _load_safe_output_names():
|
||||||
|
"""Derive the safe-output allowlist from interface.json outputs.
|
||||||
|
|
||||||
|
P14 (REQ-178): scan modules/l1/*/interface.json; an output is safe if
|
||||||
|
its spec does not set sensitive:true. Falls back to the hardcoded set
|
||||||
|
if no interfaces are readable.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
root = Path(__file__).resolve().parent.parent
|
||||||
|
safe = set()
|
||||||
|
try:
|
||||||
|
for iface in (root / "modules" / "l1").glob("*/interface.json"):
|
||||||
|
d = json.loads(iface.read_text())
|
||||||
|
outs = d.get("outputs", {})
|
||||||
|
if isinstance(outs, dict):
|
||||||
|
for name, spec in outs.items():
|
||||||
|
if not (isinstance(spec, dict) and spec.get("sensitive")):
|
||||||
|
safe.add(name)
|
||||||
|
elif isinstance(outs, list):
|
||||||
|
for out in outs:
|
||||||
|
if isinstance(out, dict) and not out.get("sensitive"):
|
||||||
|
safe.add(out.get("name", ""))
|
||||||
|
except (OSError, ValueError):
|
||||||
|
pass
|
||||||
|
return safe or _HARDCODED_SAFE_OUTPUTS
|
||||||
|
|
||||||
|
|
||||||
|
SAFE_OUTPUT_NAMES = _load_safe_output_names()
|
||||||
|
|
||||||
|
|
||||||
def _ssm_client():
|
def _ssm_client():
|
||||||
if boto3 is None:
|
if boto3 is None:
|
||||||
raise RuntimeError("boto3 is required for SSM publishing")
|
raise RuntimeError("boto3 is required for SSM publishing")
|
||||||
|
|||||||
+42
-2
@@ -7,6 +7,9 @@
|
|||||||
# run_platform.sh --plan-only <contract.yml> (AWS plan only, no Checkov/outbox)
|
# 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 --apply <contract.yml> (AWS apply: init/validate/plan/apply)
|
||||||
# run_platform.sh --destroy <contract.yml> (AWS destroy: init/validate/destroy)
|
# run_platform.sh --destroy <contract.yml> (AWS destroy: init/validate/destroy)
|
||||||
|
# run_platform.sh --local [contract.yml] (local emulating tier, no AWS)
|
||||||
|
# run_platform.sh --decommission <CR> <contract.yml> (gated teardown)
|
||||||
|
# run_platform.sh --help (show all flags)
|
||||||
#
|
#
|
||||||
# Modes:
|
# Modes:
|
||||||
# --check-only (offline, no AWS/Checkov/DynamoDB — for CI)
|
# --check-only (offline, no AWS/Checkov/DynamoDB — for CI)
|
||||||
@@ -17,13 +20,19 @@
|
|||||||
# contract -> resolver -> stack -> adapter -> terraform init/validate/plan/apply -> exit 0
|
# contract -> resolver -> stack -> adapter -> terraform init/validate/plan/apply -> exit 0
|
||||||
# --destroy (requires AWS creds; use --decommission <CR> for gated production teardown)
|
# --destroy (requires AWS creds; use --decommission <CR> for gated production teardown)
|
||||||
# contract -> resolver -> stack -> adapter -> terraform init/validate/destroy -> exit 0
|
# contract -> resolver -> stack -> adapter -> terraform init/validate/destroy -> exit 0
|
||||||
|
# --local (no AWS creds; local emulating tier D-092)
|
||||||
|
# contract -> resolver -> adapter -> local S3/ECS/outbox/Lambda stubs -> exit 0
|
||||||
# (default) (requires AWS creds + Checkov + DynamoDB)
|
# (default) (requires AWS creds + Checkov + DynamoDB)
|
||||||
# contract -> resolver -> stack -> adapter -> terraform plan -> Checkov ->
|
# contract -> resolver -> stack -> adapter -> terraform plan -> Checkov ->
|
||||||
# confidence -> outbox
|
# confidence -> outbox
|
||||||
#
|
#
|
||||||
# Flags:
|
# Flags:
|
||||||
# --quiet suppress terraform/checkov streaming (output to log only)
|
# --quiet suppress terraform/checkov streaming (output to log only)
|
||||||
# --decommission gate --destroy with D-070 two-step CR validation (requires <CR>)
|
# --decommission gate --destroy with D-070 two-step CR validation (requires <CR>)
|
||||||
|
# --deploy-uptime deploy the uptime monitoring stack (separate state)
|
||||||
|
# --local run the headline E2E against the local emulating tier (D-092)
|
||||||
|
# --environment <name> override the contract's environment at load time (D-088)
|
||||||
|
# --help, -h show all flags + a one-line description
|
||||||
#
|
#
|
||||||
# The contract file is a YAML file validated against schemas/contract.schema.json.
|
# 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
|
# The resolver (core/contract_resolver.py) resolves it to a Target Stack
|
||||||
@@ -59,6 +68,36 @@ CHANGE_REQUEST_ID=""
|
|||||||
ENVIRONMENT_OVERRIDE=""
|
ENVIRONMENT_OVERRIDE=""
|
||||||
CONTRACT=""
|
CONTRACT=""
|
||||||
|
|
||||||
|
# P15 (REQ-179): --help / -h prints all flags + a one-line description.
|
||||||
|
_print_help() {
|
||||||
|
cat <<'HELP'
|
||||||
|
Nova platform pipeline — run_platform.sh
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
run_platform.sh <contract.yml> (full e2e with AWS)
|
||||||
|
run_platform.sh --check-only [contract.yml] (offline, no AWS)
|
||||||
|
run_platform.sh --plan-only <contract.yml> (AWS plan only)
|
||||||
|
run_platform.sh --apply <contract.yml> (AWS apply)
|
||||||
|
run_platform.sh --destroy <contract.yml> (AWS destroy)
|
||||||
|
run_platform.sh --local [contract.yml] (local emulating tier)
|
||||||
|
run_platform.sh --decommission <CR> <contract.yml> (gated teardown)
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--check-only Offline validation (no AWS/Checkov/DynamoDB) — for CI
|
||||||
|
--plan-only AWS plan only (requires AWS creds, no Checkov/outbox)
|
||||||
|
--apply AWS apply: init/validate/plan/apply (HITL gate for qa/prod/dr)
|
||||||
|
--destroy AWS destroy: init/validate/destroy
|
||||||
|
--decommission Gate --destroy with D-070 two-step CR validation (requires <CR>)
|
||||||
|
--local Run the headline E2E against the local emulating tier (D-092, no AWS)
|
||||||
|
--quiet Suppress terraform/checkov streaming (log only)
|
||||||
|
--deploy-uptime Deploy the uptime monitoring stack (separate state)
|
||||||
|
--environment <name> Override the contract's environment at load time (D-088)
|
||||||
|
--help, -h Show this help
|
||||||
|
|
||||||
|
The contract file is a YAML file validated against schemas/contract.schema.json.
|
||||||
|
HELP
|
||||||
|
}
|
||||||
|
|
||||||
# Parse args; --environment takes a value (either --environment=VALUE or
|
# Parse args; --environment takes a value (either --environment=VALUE or
|
||||||
# --environment VALUE). The contract / changeRequestId are the remaining
|
# --environment VALUE). The contract / changeRequestId are the remaining
|
||||||
# positional args.
|
# positional args.
|
||||||
@@ -69,6 +108,7 @@ for arg in "$@"; do
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
|
--help|-h) _print_help; exit 0 ;;
|
||||||
--check-only) CHECK_ONLY=1 ;;
|
--check-only) CHECK_ONLY=1 ;;
|
||||||
--plan-only) PLAN_ONLY=1 ;;
|
--plan-only) PLAN_ONLY=1 ;;
|
||||||
--apply) APPLY_ONLY=1 ;;
|
--apply) APPLY_ONLY=1 ;;
|
||||||
|
|||||||
Reference in New Issue
Block a user