3 / 75 · 08 Infrastructure as Code Testing · Terraform Validation← prev⊞ allnext →☰ Read as one page
1.3Plan-Time Analysis: What Will Actually Change?
terraform plan is your integration contract -- it shows the delta between your declared state and the real world. This is where you move from "is the code valid?" to "what will the code do?"
Generating and Analyzing Plans
# Generate a plan file for downstream analysis
terraform plan -out=tfplan -detailed-exitcode
# Exit codes:
# 0 = no changes
# 1 = error
# 2 = changes present (this is the important one)
# Convert plan to JSON for programmatic analysis
terraform show -json tfplan > tfplan.json
Programmatic Plan Assertions
The JSON plan output is a goldmine for automated testing. You can write assertions against it just like any other test:
# scripts/validate_plan.py
import json
import sys
def load_plan(path="tfplan.json"):
with open(path) as f:
return json.load(f)
def check_no_destroys(plan):
"""No resources should be destroyed without explicit approval."""
destroys = [
change["address"]
for change in plan["resource_changes"]
if "delete" in change["change"]["actions"]
]
if destroys:
print(f"BLOCKED: Plan would destroy {len(destroys)} resources:")
for r in destroys:
print(f" - {r}")
return False
return True
def check_no_replacements(plan):
"""Flag resources that will be replaced (destroy + create)."""
replacements = [
change["address"]
for change in plan["resource_changes"]
if change["change"]["actions"] == ["delete", "create"]
or change["change"]["actions"] == ["create", "delete"]
]
if replacements:
print(f"WARNING: Plan will replace {len(replacements)} resources:")
for r in replacements:
print(f" - {r}")
return False
return True
def check_no_public_s3(plan):
"""S3 buckets must never have public ACLs."""
for change in plan["resource_changes"]:
if change["type"] == "aws_s3_bucket":
after = change["change"].get("after", {})
acl = after.get("acl", "private")
if acl != "private":
print(f"BLOCKED: {change['address']} has ACL '{acl}' (must be 'private')")
return False
return True
if __name__ == "__main__":
plan = load_plan()
checks = [
check_no_destroys(plan),
check_no_replacements(plan),
check_no_public_s3(plan),
]
if not all(checks):
sys.exit(1)
print("All plan checks passed.")
Common Plan Assertions to Implement
| Assertion | Why It Matters | Risk Level |
|---|---|---|
| No resource destroys | Prevents accidental data loss | Critical |
| No resource replacements on databases | RDS replacement = downtime + data risk | Critical |
| No public security group ingress | Prevents open network exposure | Critical |
| All S3 buckets encrypted | Compliance requirement | High |
| No oversized instances in non-prod | Cost control | Medium |
| All resources have required tags | Governance and cost allocation | Medium |
| No changes to IAM policies without review | Security posture | High |