terrakit

terrakit.dbhq.uk

terrakit

Read a Terraform plan and find out what it actually does

terrakit is a free, open-source command-line tool that reads a Terraform or OpenTofu plan and ranks the change by how much damage it can do. It takes a file and nothing else: no credentials, no network, no apply, and no attribute value in the output.

GoMITLinux and macOS, on amd64 and arm64

What it prints

terrakit plan.json
terrakit  1 finding  terraform 1.9.8
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CRITICAL ──────────────────────────────────────────────────────────────────  1

  azurerm_postgresql_flexible_server.main
  destroy and create
  ├ holds data, so destroying it loses that data
  ├ an attribute changed that cannot be updated in place
  └ forces replacement   zone

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1 critical

One section per severity present, most severe first, and none at all for a severity with nothing in it. The report is set to the width of your terminal, between 60 and 100 columns.

That finding is critical rather than high for one reason: the resource type holds data, so destroying it loses that data. It is the only escalation in the tool, and it applies to destruction alone.

Notice what is not there. The attribute that forced the replacement is named as zone, and neither its old value nor its new one appears, because no value ever does.

What it finds

  • What this change destroys, ranked, with the ones that lose data first
  • Why a resource is being replaced, using Terraform's own stated reason
  • Which attribute forced the replacement, by name
  • Renames that forgot a moved block: a destroy and a create that look like the same resource, which is how a refactor quietly destroys a database it meant to keep
  • Attributes whose before and after are the same value written differently: a reshuffled list, a JSON policy whose keys moved, a re-indented heredoc, a port that came back as a string, a null that became an empty list
  • When that is the whole of a resource's change, said out loud, which is the fastest way to clear an update in place that is really nothing
  • What cannot be known until apply, so you can see which claims about this change are unverifiable in review

Each of those is a statement of fact rather than a verdict. Order is significant for a container command, whitespace is significant in a script, and a tool that called either one harmless would eventually be confidently wrong about somebody's production change. terrakit says what kind of difference it found and leaves the call to you.

Three things it will not do

  • It takes a file, and runs nothing

    terrakit reads a plan file, or the same JSON piped in on standard input. It never runs terraform, never reads a cloud credential, never makes a network call and never applies anything. The only file it writes is the one you name with --out, created mode 0600 because the report lists every resource in the plan.

  • It never prints an attribute's value, in any format

    Not masked, not redacted, not truncated. Values are not in the output at all. Masking depends on Terraform having marked a value sensitive, and that marking is best-effort: a live credential was found in a real plan that Terraform had not marked. Paths, counts, levels and terrakit's own sentences are all it will ever show you.

  • It is deterministic, with no model in the loop

    The same plan always produces the same verdict. Nothing is sent anywhere, there is no model to talk you round, and no ranking that cannot be read straight off the plan. Findings are sorted most severe first with ties broken on the resource address, so two runs of the same plan are byte for byte identical.

Install

With a Go toolchain

go install github.com/dbhq-uk/terrakit/cmd/terrakit@latest

Or download a binary from the releases page (external site). The release ships terrakit and tkit, built from the same package, so use whichever name is free on your machine. Builds are for Linux and macOS, on amd64 and arm64.

Then run it

terraform plan -out tfplan
terraform show -json tfplan > plan.json
terrakit plan.json

Or skip the file entirely. Plan JSON can hold a credential in the clear, and a file on a CI runner is one more place for it to be left behind:

terraform show -json tfplan | terrakit -

Four levels, and no medium

A middle bucket is where findings go to be ignored, so there is not one.

LevelWhat reaches it
criticalA destroy or a replacement of a resource type that holds data, so destroying it loses that data. This is the only escalation in the tool, and it applies to destruction alone: updating a database in place does not lose data.
highAny other destroy, or any replacement. The resource goes away and comes back, whatever is or is not inside it.
lowAn update in place, or a resource that is being forgotten from state but left running.
infoA create, a data source read, an import and a no-op. Nothing existing is being taken away.

The source, and the tests that hold it

Every line is public and MIT licensed, and the three boundaries above are held by tests in that repository rather than by a promise on this website. It also carries the fixtures the output on this site was produced from, including a plan that exists only to be leaked from.

terrakit uses Google Analytics to count how many people read these pages. No cookie is set unless you accept, and every page works exactly the same either way.