#!/usr/bin/env python3 """Run local Terraform checks with no inherited AWS credentials or live plans.""" import datetime import hashlib import json import os from pathlib import Path import re import subprocess import sys ROOT = Path(__file__).resolve().parent REPORTS = ROOT / "reports" def main(): if len(sys.argv) != 2: raise SystemExit("Usage: python3 verify.py /absolute/path/to/terraform") terraform = Path(sys.argv[1]).resolve(strict=True) if not terraform.is_file() or not os.access(terraform, os.X_OK): raise SystemExit("Terraform must be an executable file.") test_files = list((ROOT / "tests").glob("*.tftest.hcl")) if not test_files: raise SystemExit("No mocked test files found.") for test_file in test_files: content = test_file.read_text() runs = len(re.findall(r'^run\s+"', content, re.MULTILINE)) plans = len(re.findall(r'^\s*command\s*=\s*plan\s*$', content, re.MULTILINE)) if runs != plans or not runs or re.search(r'^\s*provider\s+"', content, re.MULTILINE): raise SystemExit("Every test run must explicitly use plan and only a mocked provider.") if not re.search(r'^mock_provider\s+"aws"', content, re.MULTILINE): raise SystemExit("Every test file must declare the AWS mock provider.") # Construct a fresh environment rather than forwarding AWS/profile/web-identity secrets. environment = { "PATH": "/usr/local/bin:/usr/bin:/bin", "LANG": "C.UTF-8", "LC_ALL": "C.UTF-8", "TMPDIR": "/tmp", "TF_IN_AUTOMATION": "1", "TF_INPUT": "0", "TF_CLI_CONFIG_FILE": "/dev/null", "CHECKPOINT_DISABLE": "1", "AWS_CONFIG_FILE": "/dev/null", "AWS_SHARED_CREDENTIALS_FILE": "/dev/null", "AWS_EC2_METADATA_DISABLED": "true", } REPORTS.mkdir(exist_ok=True) report = { "started_at_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(), "terraform_sha256": hashlib.sha256(terraform.read_bytes()).hexdigest(), "scope": "Local validation and mock-provider plan-only tests; no AWS deployment.", "inherited_environment_forwarded": False, "aws_config_files": "/dev/null", "aws_metadata_disabled": True, "commands": [], } commands = [ ("version", ["version", "-json"]), ("format", ["fmt", "-recursive"]), ("format-check", ["fmt", "-check", "-recursive"]), ("init", ["init", "-backend=false", "-input=false", "-no-color"]), ("validate", ["validate", "-json"]), ("test", ["test", "-no-color"]), ] for name, arguments in commands: started = datetime.datetime.now(datetime.timezone.utc) result = subprocess.run([str(terraform), *arguments], cwd=ROOT, env=environment, capture_output=True, text=True) output = result.stdout + result.stderr logfile = REPORTS / f"{name}.log" logfile.write_text(output) report["commands"].append({ "name": name, "arguments": arguments, "returncode": result.returncode, "elapsed_seconds": round((datetime.datetime.now(datetime.timezone.utc) - started).total_seconds(), 3), "log": str(logfile.relative_to(ROOT)), }) if name == "version" and result.returncode == 0: report["terraform_version"] = json.loads(result.stdout)["terraform_version"] if name == "test": summary = re.search(r"(\d+) passed, (\d+) failed", output) if summary: report["mock_test_runs"] = {"passed": int(summary[1]), "failed": int(summary[2])} report["passed"] = all(item["returncode"] == 0 for item in report["commands"]) and len(report["commands"]) == len(commands) report["finished_at_utc"] = datetime.datetime.now(datetime.timezone.utc).isoformat() (REPORTS / "verification.json").write_text(json.dumps(report, indent=2) + "\n") print(f"{name}: exit {result.returncode}; {logfile.relative_to(ROOT)}", flush=True) if result.returncode: print(output[-12000:], flush=True) return result.returncode print(json.dumps({"passed": report["passed"], "mock_test_runs": report.get("mock_test_runs"), "report": str(REPORTS / "verification.json")})) return 0 if __name__ == "__main__": raise SystemExit(main())