shoc-backend/scripts/check-terraform-import-plan.py
Adam Moussa 25c2e84e8f feat(terraform): adopt live deployment roles safely
Add import-only dev and staging state with least-privilege HCP authentication and plan safety guards.
2026-08-28 19:12:34 -04:00

74 lines
2.1 KiB
Python

#!/usr/bin/env python3
"""Reject unsafe actions in a live Terraform import plan."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
ALLOWED_MANAGED_TYPES = {"aws_iam_role", "aws_iam_role_policy"}
UNSAFE_ACTIONS = {"create", "delete"}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("plan_json", type=Path)
parser.add_argument(
"--allow-update",
action="store_true",
help="Allow in-place updates after the initial no-op import is proven.",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
plan = json.loads(args.plan_json.read_text(encoding="utf-8"))
violations: list[str] = []
managed = 0
updates = 0
for resource in plan.get("resource_changes", []):
if resource.get("mode", "managed") != "managed":
continue
resource_type = resource.get("type", "")
address = resource.get("address", "<unknown>")
actions = set(resource.get("change", {}).get("actions", []))
managed += 1
if resource_type not in ALLOWED_MANAGED_TYPES:
violations.append(
f"{address}: managed type {resource_type!r} is outside the live ownership boundary"
)
unsafe = sorted(actions & UNSAFE_ACTIONS)
if unsafe:
violations.append(f"{address}: unsafe actions {unsafe}")
if "update" in actions:
updates += 1
if not args.allow_update:
violations.append(
f"{address}: update is forbidden during the initial no-op import"
)
if violations:
print("FAIL: live Terraform plan is not import-safe", file=sys.stderr)
for violation in violations:
print(f" - {violation}", file=sys.stderr)
return 1
mode = "controlled update" if args.allow_update else "no-op import"
print(
f"PASS: {mode} plan has {managed} managed resources, "
f"{updates} updates, and no create/delete/replace actions"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())