Terraform 计划的终端用户界面
An interactive terminal UI for reading Terraform plans.
A Terraform configuration is a graph, and a plan is a proposed change over
that graph. Scrolling through hundrends of lines of terraform plan output
makes that structure almost impossible to see. tfplantui renders the plan as
its dependency graph — laid out in columns by dependency depth — and lets you
walk it with the arrow keys, expanding any resource to see a color-coded
before → after diff of its attributes.
…
terraform plan gives you its answer as plain text — and for any real
change that means hundreds of lines of diff to scroll through, or the raw plan
JSON to parse yourself. Either way, you are the one reconstructing the plan's
shape in your head. tfplantui does that for you: it turns the plan into a
graph you can navigate, so a human can visualize and reason through every
change at once instead of reading a transcript.
←/→ follows the
edges, so you can trace what depends on what.space on any resource to unfold its attribute diff,
color-coded the way you'd expect: green additions, red removals, amber
in-place changes, and (known after apply) for computed values.t) plus a
changes-only filter (n) keep big plans tractable.As a CLI:
go install github.com/omarismail/terraform-plan-tui/cmd/tfplantui@latest
# or build from a checkout
go build -o tfplantui ./cmd/tfplantui
Requires Go 1.24+. Terraform is only needed if you hand it a binary plan file (see below); reading plan JSON needs nothing else.
tfplantui is also a package, so any Go CLI can embed the same plan explorer
behind its own flag. Hand it a plan file and the rest — loading binary plans via
terraform show -json, parsing, layout, and the interactive UI — is handled for
you:
import "github.com/omarismail/terraform-plan-tui"
// In your own command / flag handler:
func runPlanExplorer(planPath string) error {
// planPath may be a binary plan file, plan JSON, or "-" for stdin.
return tfplantui.Run(planPath)
}
The public surface is intentionally small:
// Launch the interactive TUI, blocking until the user quits.
func Run(path string, opts ...Option) error
// Same, but on plan JSON you already hold in memory.
func RunJSON(planJSON []byte, opts ...Option) error
// Write the non-interactive text summary instead (handy for CI).
func Summary(w io.Writer, path string, opts ...Option) error
// Options: WithAltScreen, WithTerraformPath, WithPlanName, WithProgramOptions
tfplantui reads the JSON form of a plan (terraform show -json). You can
point it at any of three things:
# 1. a binary plan file — tfplantui runs `terraform show -json` for you
terraform plan -out=plan.tfplan
tfplantui plan.tfplan
# 2. plan JSON you produced yourself
terraform show -json plan.tfplan > plan.json
tfplantui plan.json
# 3. piped on stdin
terraform show -json plan.tfplan | tfplantui -
# non-interactive text summary (great for CI / quick triage)
tfplantui -summary plan.json
Try it immediately from a checkout — no install needed:
go run ./cmd/tfplantui examples/medium/plan.json
| Key | Action |
|---|---|
↑ ↓ / k j |
move within a dependency layer (column) |
← → / h l |
move across layers, following dependency edges |
space / enter |
expand / collapse the selected resource's diff |
[ ] |
cycle instances of a count/for_each resource |
u |
show / hide unchanged attributes |
n |
changes-only — hide no-op resources |
x |
collapse everything |
t |
switch between graph and list views |
g / G |
jump to top / bottom of the column or list |
/ |
search by resource address (Enter jumps, Esc cancels) |
? |
toggle help |
q / Ctrl-C |
quit |
Color carries one thing — the action — so it stays legible at a glance:
| Glyph | Meaning |
|---|---|
+ green |
create |
± purple |
replace (destroy then create) |
- red |
destroy |
~ amber |
update in place |
* yellow |
mixed — a count/for_each block whose instances differ |
· dim |
no change (recedes into the background) |
Everything else is deliberately neutral so it doesn't compete with the action colors:
─▶), instead of recoloring
other nodes. The footer also lists them by name.Three plans are checked in under examples/, all using only the
hashicorp/random provider so they are fully local and reproducible:
| Example | Shape | Changes |
|---|---|---|
simple |
6 resources, 3 layers, light dependencies | a fresh plan — all creates |
medium |
~10 blocks / 28 instances, wide + 3 layers | create / replace / delete / no-op |
large |
64 blocks / ~400 instances, very wide + 5 layers | ~200 to add, ~180 to destroy, 138 replace |
Each plan.json is committed, so the examples run without Terraform. To
regenerate them (this does require Terraform):
examples/generate.sh all # or: simple | medium | large
Each example directory holds:
providers.tf — the required_providers block (always loaded),main.tf — the after world (what the plan ends at; this is the readable config),before.tf.txt — the optional before world, applied first to seed prior
state so the plan contains updates/replaces/deletes (it is not a .tf file, so
Terraform ignores it until generate.sh swaps it in),plan.json — the generated, committed artifact.The large example's configs are emitted by examples/large/gen_config.py.
internal/tfplan): reads resource_changes and the configuration
block from the plan JSON. Instances (count/for_each) are grouped into their
resource block, which becomes one graph node.configuration.expressions. Nodes are assigned to columns by
longest dependency path (a Sugiyama-style layering), so depth reads
left → right.internal/tfplan/diff.go): walks before / after together,
threading after_unknown (for (known after apply)) and the
before_sensitive/after_sensitive trees (sensitive values are redacted, never
printed), recursing into nested maps such as keepers.internal/ui): a Bubble Tea
model. Rendering is windowed — only the visible columns and rows are drawn — so
large plans stay responsive.module.x.foo wired from a root resource) are not drawn; intra-module
edges and direct resource references are. Resources are always shown; only that
one class of edge may be missing.random provider, whose attributes are almost all
force-new, so they demonstrate create/replace/delete rather than many in-place
~ updates. In-place updates render correctly (see the tests) — point the tool
at a real plan to see them.go test ./... # unit + headless-TUI tests
TFPLAN_DUMP=1 go test ./internal/ui -run TestDumpVisual -v # eyeball the layout
MIT — see LICENSE.
暂无开放 Issues,或尚未同步最近议题。