"""Nova Infracost Post-Processor (REQ-187, D-120). Runs Infracost on `terraform show -json plan.tfplan` (offline, reads plan JSON, no live AWS). Emits nova.cost.estimated{delta_usd} events. Degrades gracefully (omits the event, logs a warning) when Infracost CLI is absent (assumption A6). run_platform.sh invokes it after the plan stage. """ import json import os import shutil import subprocess import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) from core.metrics.event_envelope import emit def _is_infracost_available(): """Check if the Infracost CLI is on PATH.""" return shutil.which("infracost") is not None def estimate(plan_json_path, run_id, contract_id, environment): """Run Infracost on a terraform plan JSON. Returns the cost estimate dict. Args: plan_json_path: path to `terraform show -json plan.tfplan` output run_id: the run identifier contract_id: the contract UUID environment: dev|qa|prod|dr Returns: {"delta_usd": float, "total_monthly_usd": float, "available": bool} or {"available": False} if Infracost is not installed. """ if not _is_infracost_available(): sys.stderr.write("[infracost] CLI not found — cost.estimated event omitted (A6 degraded mode)\n") return {"available": False, "delta_usd": 0.0, "total_monthly_usd": 0.0} if not os.path.isfile(plan_json_path): sys.stderr.write(f"[infracost] plan JSON not found: {plan_json_path}\n") return {"available": False, "delta_usd": 0.0, "total_monthly_usd": 0.0} try: result = subprocess.run( ["infracost", "breakdown", "--path", plan_json_path, "--format", "json"], capture_output=True, text=True, timeout=30, ) if result.returncode != 0: sys.stderr.write(f"[infracost] CLI failed: {result.stderr[:200]}\n") return {"available": False, "delta_usd": 0.0, "total_monthly_usd": 0.0} breakdown = json.loads(result.stdout) delta = float(breakdown.get("diffTotalMonthlyCost", 0.0)) total = float(breakdown.get("totalMonthlyCost", 0.0)) estimate_data = {"available": True, "delta_usd": delta, "total_monthly_usd": total} emit("nova.cost.estimated", run_id, environment, estimate_data, contract_id=contract_id) return estimate_data except Exception as exc: sys.stderr.write(f"[infracost] error: {exc}\n") return {"available": False, "delta_usd": 0.0, "total_monthly_usd": 0.0} if __name__ == "__main__": if len(sys.argv) < 5: print("usage: infracost_adapter.py ", file=sys.stderr) sys.exit(2) est = estimate(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]) print(json.dumps(est, indent=2))