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.
What it prints
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 criticalOne 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
movedblock: 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@latestOr 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 tfplanterraform show -json tfplan > plan.jsonterrakit plan.jsonOr 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.
| Level | What reaches it |
|---|---|
critical | A 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. |
high | Any other destroy, or any replacement. The resource goes away and comes back, whatever is or is not inside it. |
low | An update in place, or a resource that is being forgotten from state but left running. |
info | A 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.