2443909362
---ci--- project: nova-platform milestone: v1.0 status: complete requirements: covered: [REQ-01,REQ-02,REQ-03,REQ-04,REQ-05,REQ-06,REQ-07,REQ-08,REQ-09,REQ-10,REQ-11,REQ-12,REQ-13,REQ-14,REQ-15,REQ-16,REQ-17,REQ-18,REQ-19,REQ-20,REQ-21,REQ-22,REQ-23,REQ-24,REQ-25,REQ-26,REQ-27,REQ-28,REQ-29,REQ-30,REQ-31,REQ-32,REQ-33,REQ-34,REQ-35,REQ-36,REQ-37,REQ-38] partial: [] ---/ci--- v1.0 milestone complete: simplified infrastructure-delivery platform derived from Nova (acdl). 6 phases (P0-P5 + P6 final). 38 REQ-IDs. 38 decisions (D-001..D-038). 76 tests pass. Engine boundary holds. Happy paths green (check-only + CI). 13 L1 + 2 L2 modules. 5 terraform roots. Shell reproducibility. Zero OOS files. Tags: v0.1.0 (P0) → v0.1.1..v0.1.5 (P1..P5) → v0.1.6 (P6 = milestone release on v0.1 patch line).
102 lines
3.4 KiB
Python
102 lines
3.4 KiB
Python
"""Nova Platform — Terraform Adapter.
|
|
|
|
The ONLY engine-specific code in the platform (per REQ-09, verified by
|
|
tests/test_engine_boundary.py). Loads modules/registry.json internally
|
|
to map module -> terraform_dir (per D-037/C-1 grill fix — the resolver
|
|
does NOT put a `source` field in the stack; the adapter resolves it
|
|
here, inside the engine boundary).
|
|
|
|
Stateless assembler: no `terraform` CLI invocation, no state files, no
|
|
plan files. Emits Terraform HCL: one `module "x" { source = ...; <inputs> }`
|
|
block per stack resource.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
|
|
def _load_registry(repo_root):
|
|
"""Load modules/registry.json -> {module_name: terraform_dir}."""
|
|
registry_path = os.path.join(str(repo_root), "modules", "registry.json")
|
|
with open(registry_path) as fh:
|
|
registry = json.load(fh)
|
|
return {name: list(versions.values())[0].get("terraform_dir")
|
|
for name, versions in registry.items()
|
|
if list(versions.values())[0].get("terraform_dir")}
|
|
|
|
|
|
def _tf_value(value):
|
|
"""Render a Python value as an HCL expression."""
|
|
if isinstance(value, bool):
|
|
return "true" if value else "false"
|
|
if isinstance(value, (int, float)):
|
|
return str(value)
|
|
if isinstance(value, list):
|
|
return "[" + ", ".join(_tf_value(v) for v in value) + "]"
|
|
if isinstance(value, dict):
|
|
return "{ " + ", ".join(f"{k} = {_tf_value(v)}" for k, v in value.items()) + " }"
|
|
return json.dumps(str(value))
|
|
|
|
|
|
def _emit_module_block(resource, terraform_dirs, repo_root):
|
|
"""Emit one `module "x" { source = ...; <inputs> }` block."""
|
|
module_name = resource["module"]
|
|
rid = module_name.replace("-", "_")
|
|
tf_dir = terraform_dirs.get(module_name)
|
|
if tf_dir is None:
|
|
raise ValueError(f"module '{module_name}' has no terraform_dir in registry")
|
|
source = os.path.join(str(repo_root), tf_dir)
|
|
lines = [f'module "{rid}" {{', f' source = "{source}"']
|
|
for key, val in resource.get("inputs", {}).items():
|
|
if key == "region":
|
|
continue
|
|
lines.append(f" {key} = {_tf_value(val)}")
|
|
lines.append("}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def adapt(stack, repo_root):
|
|
"""Compile a flat stack dict to Terraform HCL.
|
|
|
|
Args:
|
|
stack: a flat stack dict conforming to schemas/stack.schema.json
|
|
(NO `source` field per D-037 — the adapter resolves
|
|
module -> terraform_dir via the registry).
|
|
repo_root: Path to the repo root (the adapter loads
|
|
modules/registry.json from here).
|
|
|
|
Returns:
|
|
A string of Terraform HCL with one `module "x" {}` block per
|
|
stack resource.
|
|
"""
|
|
terraform_dirs = _load_registry(repo_root)
|
|
blocks = []
|
|
for resource in stack.get("resources", []):
|
|
blocks.append(_emit_module_block(resource, terraform_dirs, repo_root))
|
|
return "\n\n".join(blocks) + "\n"
|
|
|
|
|
|
def main(argv=None):
|
|
import sys
|
|
argv = argv or sys.argv[1:]
|
|
if len(argv) < 1:
|
|
print("usage: adapter.py <stack.json> [out.tf]", file=sys.stderr)
|
|
return 2
|
|
stack_path = argv[0]
|
|
out_path = argv[1] if len(argv) > 1 else None
|
|
repo_root = Path(__file__).resolve().parent.parent.parent
|
|
with open(stack_path) as fh:
|
|
stack = json.load(fh)
|
|
hcl = adapt(stack, repo_root)
|
|
if out_path:
|
|
with open(out_path, "w") as fh:
|
|
fh.write(hcl)
|
|
else:
|
|
print(hcl)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
sys.exit(main()) |