tfplantui

package module
v0.0.0-...-fc76395 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: MIT Imports: 11 Imported by: 0

README

tfplantui

An interactive terminal UI for reading Terraform plans.

tfplantui demo

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.

 Layer 0                          Layer 1                          Layer 2
╭──────────────────────────────╮  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓  ╭──────────────────────────────╮
│ · random_id.seed             │  ┃ ± random_string.api_key ×4   ┃  │ ± random_password.master     │
╰──────────────────────────────╯  ┃ keepers:                     ┃  ╰──────────────────────────────╯
╭──────────────────────────────╮  ┃   ~ rotation = "v1" → "v2"   ┃  ╭──────────────────────────────╮
│ · random_pet.org             │  ┃ ~ result = "abc…" → (known…) ┃  │ ± random_pet.release         │
╰──────────────────────────────╯  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛  ╰──────────────────────────────╯

Why

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.

  • Dependency layout. Resources with no dependencies sit on the left; each column to the right depends on the one before it. Moving / follows the edges, so you can trace what depends on what.
  • Expand in place. Hit 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.
  • Built for small and large plans. A six-resource plan and a 400-instance, five-layer plan use the same view; columns scroll vertically, the layout scrolls horizontally, and a flat list view (t) plus a changes-only filter (n) keep big plans tractable.

Install

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.

Use as a library

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

Usage

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

Keys

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 legend

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:

  • The cursor is a dark card with bright white text — it reads by contrast, not hue.
  • Unchanged (no-op) resources are dimmed almost to the background; they only brighten when you move the cursor onto them.
  • Dependencies are drawn on demand: selecting a resource draws arrows from its dependencies into it and out to its dependents (─▶), instead of recoloring other nodes. The footer also lists them by name.

Examples

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.

How it works

  • Parse (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.
  • Graph: dependency edges come from the references Terraform records in each resource's configuration.expressions. Nodes are assigned to columns by longest dependency path (a Sugiyama-style layering), so depth reads left → right.
  • Diff (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.
  • UI (internal/ui): a Bubble Tea model. Rendering is windowed — only the visible columns and rows are drawn — so large plans stay responsive.

Limitations

  • Cross-module dependency edges that pass through a module input variable (e.g. 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.
  • The bundled examples use the 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.

Development

go test ./...                                   # unit + headless-TUI tests
TFPLAN_DUMP=1 go test ./internal/ui -run TestDumpVisual -v   # eyeball the layout

License

MIT — see LICENSE.

Documentation

Overview

Package tfplantui renders a HashiCorp Terraform plan as an interactive terminal UI: a dependency graph laid out in columns by dependency depth, with expandable, color-coded before→after attribute diffs.

It is designed to drop into any CLI. Give it a plan and it takes over the terminal until the user quits — the loading (binary plan file vs. plan JSON vs. stdin) is handled for you. The whole integration is usually one line from your own flag handler:

func runPlanFlag(path string) error { return tfplantui.Run(path) }

For a non-interactive overview (CI, quick triage) use Summary; if you already hold plan JSON in memory use RunJSON.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(path string, opts ...Option) error

Run loads a Terraform plan from path and launches the interactive TUI, blocking until the user quits. path may be:

  • a binary plan file from "terraform plan -out=plan.tfplan" — "terraform show -json" is invoked to render it (see WithTerraformPath),
  • the JSON from "terraform show -json plan.tfplan", or
  • "-" to read plan JSON from stdin.

func RunContext

func RunContext(ctx context.Context, path string, opts ...Option) error

RunContext is Run with a context: cancelling ctx tears the UI down and returns. Normal quit (q / Ctrl-C) is unaffected. Use it when embedding the explorer in a longer-lived program that owns signal handling or a deadline.

func RunJSON

func RunJSON(planJSON []byte, opts ...Option) error

RunJSON launches the interactive TUI on plan JSON (the output of "terraform show -json") that you already hold in memory, blocking until the user quits. The source label defaults to "plan"; override it with WithPlanName.

func RunJSONContext

func RunJSONContext(ctx context.Context, planJSON []byte, opts ...Option) error

RunJSONContext is RunJSON with a context; see RunContext for the semantics.

func Summary

func Summary(w io.Writer, path string, opts ...Option) error

Summary writes a concise, non-interactive text overview of the plan at path to w instead of launching the TUI. path accepts the same forms as Run. Color is emitted only when w is a terminal.

Types

type Option

type Option func(*config)

Option configures Run, RunJSON, and Summary.

func WithAltScreen

func WithAltScreen(v bool) Option

WithAltScreen controls whether the TUI takes over the full screen (the terminal's alternate screen buffer). The default is true; pass false to keep the plan in the normal scrollback.

func WithPlanName

func WithPlanName(name string) Option

WithPlanName overrides the source label shown in the UI. By default this is the base name of the plan file ("stdin" for "-", or "plan" for RunJSON, which has no path to derive a name from).

func WithProgramOptions

func WithProgramOptions(opts ...tea.ProgramOption) Option

WithProgramOptions appends extra Bubble Tea program options (for example tea.WithMouseCellMotion). The alternate-screen option is managed by WithAltScreen and should not be passed here.

func WithTerraformPath

func WithTerraformPath(path string) Option

WithTerraformPath overrides the terraform binary used to render a *binary* plan file via "terraform show -json". By default "terraform" is looked up on PATH. It is ignored when the input is already plan JSON or stdin.

Directories

Path Synopsis
cmd
tfplantui command
Command tfplantui is an interactive terminal UI for exploring a Terraform plan as a dependency graph with color-coded, expandable attribute diffs.
Command tfplantui is an interactive terminal UI for exploring a Terraform plan as a dependency graph with color-coded, expandable attribute diffs.
internal
tfplan
Package tfplan parses the JSON output of `terraform show -json <planfile>` into a navigable domain model: a block-level dependency graph plus per-attribute before/after diffs.
Package tfplan parses the JSON output of `terraform show -json <planfile>` into a navigable domain model: a block-level dependency graph plus per-attribute before/after diffs.
ui

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL