agentleFS
riteshsonawane1372riteshsonawane1372/devops-skills/skills/terraform/terraform-review/SKILL.md

terraform-review

Skill0 starsChanged 2 months agoApache-2.0View on GitHub

Review a Terraform or OpenTofu plan and its code before apply - find resource replacements, data loss, drift, hardcoded secrets, unpinned providers, missing lifecycle guards, and blast radius that the diff does not make obvious. Use whenever reviewing a terraform plan output, an infrastructure pull request, or a module before it is used in production.

7 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use observability.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • OpenAISkill

    observability

    Vercel Observability expert guidance — Drains (logs, traces, speed insights, web analytics), Web Analytics, Speed Insights, runtime logs, custom events, OpenTelemetry integration, and monitoring dashboards. Use when instrumenting, debugging, or optimizing application performance and user experience on Vercel.

    Also in 3 projects that use this

The file

We split this skill into the rules it gives an agent

4 rules. 0 are common, 1 is distinctive, and 3 are very specific to this project.

  • 0

    Common

    Most of the 7 edited versions we read keep these.

  • 1

    Distinctive

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Read a plan for what it will destroy, not for what it will create.

    line 25
  2. Very specific:

    Always plan to a file. A plan you review and an apply you run from a re-planned state are two different changes.

    line 73
  3. Very specific:

    Run this after the execution completes.

    line 245
  4. Very specific:

    Bad: Use -target to avoid an unwanted change.

    line 263

“Common” is measured: kept by at least 6 in 10 of the 7 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Terraform Review

Read a plan for what it will destroy, not for what it will create.

Purpose

Use this when:

  • Reviewing a terraform plan before it is applied.
  • Reviewing an infrastructure pull request.
  • Assessing a module before adopting it.
  • Someone asks "is this change safe?"

Do not use this when:

  • You are executing the change — use terraform-safe-changes, which covers the apply procedure.
  • The plan or apply is erroring — use terraform-troubleshooting.

What this skill assumes: access to the plan output, and ideally the machine-readable plan (terraform show -json). Reviewing the human-readable diff alone is error-prone on large plans; the JSON form is what you should reason from.

Operating Procedure

  1. Read the summary line first. Plan: 3 to add, 1 to change, 2 to destroy. The destroy count is the only number that can end your day badly.
  2. Enumerate every destroy and every replace. A replace (-/+) is a destroy followed by a create; for a database, a disk, or anything holding state, that is data loss.
  3. For each replacement, find the forcing attribute. Terraform prints # forces replacement. Decide whether that attribute had to change.
  4. Assess blast radius. What depends on the replaced resource? A replaced security group means every attached instance loses its rules for the duration.
  5. Review the code, not only the diff: provider pinning, secrets, lifecycle blocks, count/ for_each keys, remote state.
  6. Check for drift. Anything the plan wants to change that nobody in the PR intended is drift — someone changed it in the console, and applying will revert it.
  7. Produce a verdict: safe, safe-with-conditions, or do-not-apply, with the specific reason.

Everything you state must be traceable to the plan or the code. If the plan does not show a value ((known after apply)), say so — do not assert what it will be.

Investigation

Step 1 - generate a reviewable plan

terraform plan -out=tfplan -lock-timeout=5m
terraform show -json tfplan > tfplan.json

Always plan to a file. A plan you review and an apply you run from a re-planned state are two different changes; terraform apply tfplan guarantees you apply exactly what was reviewed.

-lock-timeout avoids the common failure where a concurrent run holds the state lock and the plan fails after several minutes.

Step 2 - extract the destructive actions

jq -r '.resource_changes[]
  | select(.change.actions | index("delete"))
  | "\(.change.actions | join(",")) \(.address)"' tfplan.json

This is the highest-value command in the skill. It lists every resource that will be deleted, including replacements, which the human-readable output scatters across hundreds of lines.

jq -r '.resource_changes[]
  | select(.change.actions == ["delete","create"] or .change.actions == ["create","delete"])
  | .address' tfplan.json

Replacements specifically. ["create","delete"] is create_before_destroy, which is safer; ["delete","create"] means there is a gap with nothing there.

Step 3 - find why each replacement is forced

terraform show tfplan | grep -B3 'forces replacement'

Then ask, for each one, whether the change was intended. The common causes:

Forcing attributeUsually meansWhat to do
name / identifier changedA naming convention change, or a variable typoRename via moved block or import, not replacement
availability_zone, subnet_idA network redesignConfirm the data can move
engine_version on a major bumpSome providers replace rather than upgrade in placeUse the provider's in-place upgrade path
user_data on an EC2 instanceExpected for immutable infrastructureFine, if the instance is stateless
An attribute that is (known after apply) upstreamA dependency is being replaced and cascadingFix the upstream, the cascade disappears

A replacement forced by an attribute nobody edited is the signature of provider version drift: the provider now reads or normalises that attribute differently. Check whether the provider version changed in the lockfile.

Step 4 - detect drift

terraform plan -refresh-only

-refresh-only shows what changed in the real world versus state, with no code changes applied. If it reports differences, someone modified infrastructure outside Terraform. Decide deliberately: adopt the change into code, or let the apply revert it. Reverting an emergency console fix during an unrelated apply is a classic self-inflicted outage.

Step 5 - review the code

grep -rnE '(password|secret|token|api_key|private_key)\s*=\s*"' --include='*.tf' .
grep -rn 'required_providers' -A8 --include='*.tf' . | grep -E 'version'
test -f .terraform.lock.hcl && echo "lockfile present" || echo "NO LOCKFILE"

Checklist:

ItemBadWhy
Provider versionsversion = ">= 4.0"An unbounded constraint means a provider release can change your plan with no code change. Use ~> 4.67.
LockfileAbsent or gitignoredWithout it, CI and laptops resolve different provider versions.
SecretsLiteral strings in .tf or .tfvars committedThey are in Git history and in state forever.
BackendLocal stateNo locking, no sharing, no recovery.
State encryptionBucket without encryption/versioningState contains every secret in plaintext.
lifecycleNo prevent_destroy on databases and state bucketsThe one guard that stops an accidental destroy.
countcount = length(var.list) on stateful resourcesRemoving a middle element re-indexes and replaces everything after it. Use for_each with stable keys.
ModulesSourced from a branch (?ref=main)Not reproducible. Pin to a tag or commit.
Hardcoded account IDs / regionsLiterals scattered through resourcesBlocks reuse and hides which environment is targeted.

Step 6 - confirm the target

terraform workspace show
terraform state list | head
grep -rn 'backend' -A6 --include='*.tf' .

Confirm which environment this plan targets before approving it. A plan that looks reasonable against staging and catastrophic against production is the same text.

Decision Tree

Reviewing a plan
├── destroy count == 0 and no replacements
│   └── Review code quality and drift, then approve
├── replacements present
│   ├── Resource holds data (database, disk, bucket, PVC)
│   │   └── DO NOT APPLY until a verified backup exists and downtime is agreed
│   ├── Resource is in the request path (LB, security group, DNS)
│   │   └── Check create_before_destroy; without it there is a hard outage window
│   └── Resource is stateless and replaceable
│       └── Acceptable; confirm the forcing attribute was intended
├── destroys present without a corresponding create
│   ├── Intended removal → confirm nothing else references it
│   └── Unintended → usually a for_each key change or a moved/renamed resource
└── plan shows changes nobody in the PR made
    └── Drift. Resolve before applying, or the apply reverts someone's fix.

Common Causes

FindingSignalActionRisk
Silent database replacement-/+ aws_db_instance with identifier forcingRename via moved, never applydo-not-apply
count re-indexingMany unrelated resources replaced after a list editMigrate to for_eachreview
Provider upgrade driftReplacements with no code changePin the provider; read its changelogreview
Secret in codegrep hit in .tfRotate the secret; move to a secret storedo-not-apply
Unpinned module ref?ref=mainPin to a tagreview
Missing prevent_destroyNo lifecycle block on stateful resourcesAdd before the next changereview
Console drift-refresh-only shows changesAdopt or revert deliberatelyreview

Safety

Read-only

Everything in this skill. terraform plan, terraform show, terraform state list, jq and grep do not modify infrastructure.

Two caveats worth stating explicitly, because "plan is read-only" is only mostly true:

  • terraform plan refreshes state by default, which writes to the state file and takes the state lock. On a shared backend this can block a colleague's apply. Use -lock-timeout and, where you only want the diff, -refresh=false.
  • terraform plan reads every provider credential and can write the resulting values, including secrets, into the plan file. tfplan and tfplan.json are sensitive artifacts. Do not commit them or attach them to a public issue.

Potentially disruptive

This skill runs nothing that changes infrastructure. It does, however, tell you to execute the reviewed plan with terraform apply tfplan — which is a mutating action, and is why this skill carries the mutating risk tier rather than read-only.

The moment you move from review to execution, switch to terraform-safe-changes, which covers backups, staged applies, guard rails and rollback.

Destructive

Reviewing is not destructive. The output of a review frequently is: if this review concludes a resource must be destroyed, that execution belongs to terraform-safe-changes, with a verified backup, an agreed window, and named approval. Never approve a plan containing a destroy of a stateful resource on the basis of the diff alone.

Verification

A review is verified by what the execution reports back. This skill ends at the verdict; the execution belongs to terraform-safe-changes, and these are the numbers to compare against.

Reviewed: plan  - 3 to add, 1 to change, 0 to destroy
Executed: apply - 3 added, 1 changed, 0 destroyed

Any difference between those two lines means the applied change was not the reviewed change — someone re-planned, a provider updated, or another apply landed in between. Treat it as a finding, not as rounding.

terraform plan -detailed-exitcode

Run this after the execution completes. -detailed-exitcode returns 0 for no changes, 2 for changes pending, 1 for an error. A clean re-plan returning 0 proves the configuration converged. A re-plan that still shows changes means the configuration is not idempotent — a resource is fighting the provider or an external system, and it will produce a diff on every future run.

Then verify the resources themselves in the cloud provider, not only in Terraform. Terraform reports what it believes; the provider API reports what exists.

Anti-Patterns

Bad: Skim the plan, see "mostly adds", approve. Better: Grep for destroy and forces replacement first. On a 2000-line plan the one dangerous line is invisible to skimming, which is exactly how production databases get replaced.

Bad: Apply from a fresh plan after reviewing an older one. Better: terraform plan -out=tfplan then terraform apply tfplan. Between the two, someone else's merge or a provider update can change what gets applied.

Bad: Use -target to avoid an unwanted change. Better: -target leaves state partially applied and hides the real problem. Fix the configuration. -target is for recovering from a failed apply, not for shaping a normal one.

Bad: Ignore drift because the plan "will fix it". Better: Drift is usually somebody's emergency fix. Applying over it reverts the fix, often at the worst possible moment. Find out who changed it and why, first.

Bad: Approve because CI's plan was green. Better: CI proves the plan succeeded, not that its contents are acceptable. Green CI on a plan that destroys a database is still a green plan that destroys a database.

Composition

  • terraform-safe-changes — the execution procedure once the review passes.
  • terraform-troubleshooting — state, lock and provider errors.
  • secrets-management-review — when the review finds credentials in code or state.
  • aws-production-debugging — when the plan touches live AWS resources and you need to assess real blast radius.

References

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.