DEV Community

Veems
Veems

Posted on

permadiff: separate Terraform plan noise from real changes — and fix it at the source

Every Terraform user knows the plan that shows an "update in place" which isn't a change at all: an IAM policy whose JSON got reordered, a security-group rule list shuffled, "80" that became 80, a tags = {} that flipped to null. The before and after are semantically identical — AWS stores the value in its own canonical form — but the plan never goes away. The usual "fix" is a reflexive lifecycle { ignore_changes = [...] }, which makes the diff disappear and silently hides any real future change to that attribute.

So I wrote permadiff to stop doing that.

What it does

permadiff reads terraform show -json output and, for every in-place update, tries to prove the diff is cosmetic by canonicalising both sides — sorting policy statements, treating set-semantic lists as sets, coercing scalar types, normalising DNS names, and so on. If (and only if) the canonical before and after are identical, it labels the attribute a no-op, explains in plain English why AWS treats them as equal, and suggests the fix that makes the diff disappear at the source. Real changes are listed plainly and never suppressed.

terraform show -json plan.tfplan | permadiff
Enter fullscreen mode Exit fullscreen mode

One rule: prove it or it's real

A false negative (missed noise) is fine; a false positive (a real change mislabelled as noise) is not — that's how a tool gets someone owned. So every pattern ships with a look-alike "real twin" fixture that must stay classified as real, and the canonicalisers fail closed on anything they don't fully understand.

Take the most common perma-diff there is. You apply an IAM policy; AWS stores the statements back in a different order; the next terraform plan shows the whole
policy = jsonencode(...) as an update-in-place — a wall of diff over a document that means exactly the same thing. permadiff sorts the statements (order is insignificant in IAM), proves the before and after are identical, and reports a no-op.

Now the thin ice: a genuine permission change surfaces in that same ~ policy block. Change one Resource from arn:aws:s3:::app-data/Backups/* to .../exports/* and the plan looks almost exactly like the harmless reorder — but it isn't. Guess that "policy diffs are usually just noise" and you've quietly shipped a real change. permadiff canonicalises both sides and compares them: the reorder collapses to equal, the resource edit doesn't — so it stays a real change.

Details

It's fully offline and deterministic — rule-based against a YAML pattern catalogue compiled into a single static Go binary. No network calls, no telemetry, never edits your files. v1 covers 13 AWS pattern families (IAM/S3/KMS/SQS policy JSON, security-group rule sets, tags, ECS container definitions, AWS Batch job definitions, type coercion, Route 53 names, set-semantic lists, computed-field churn, curated JSON-document attributes). Other providers' resources pass through
untouched as real changes.

go install github.com/itsveems/permadiff/cmd/permadiff@latest
Enter fullscreen mode Exit fullscreen mode

GitHub logo itsveems / permadiff

Separate Terraform plan noise from real changes — and fix the noise at its source.

permadiff

ci

Separate Terraform plan noise from real changes — and fix the noise at its source.

permadiff separating perma-diff noise from real changes in a Terraform plan, with a fix for each no-op

Reproduce this output: permadiff examples/demo-plan.json

permadiff reads terraform show -json output and identifies perma-diffs update-in-place changes that are not real changes — artifacts of provider normalisation, JSON reordering, type coercion, or formatting, where before and after are semantically identical. For each one it explains why it's a no-op and suggests the correct fix, so the diff disappears instead of getting buried under a reflexive ignore_changes.

permadiff  6 changes: 2 perma-diff no-ops (with fixes) · 4 real changes
PERMA-DIFF NOISE (2) — semantically identical before and after; nothing changes in AWS
──────────────────────────────────────────────────────────────────────────────

  ~ aws_iam_policy.app_data
      • policy — IAM policy JSON normalisation
        AWS stores IAM policy documents in its own normalised form: object keys reordered,
        whitespace stripped, single-element arrays and scalars interchanged, and statements
        reordered. None of that changes what the policy allows

It's deliberately conservative — it'll miss noise rather than risk mislabelling a real change. If you have a perma-diff it doesn't catch, contributing is usually just YAML + two fixtures (the no-op and its real twin), no Go required. I'd love to hear which perma-diffs hurt the most in your stacks.

Top comments (0)