Documentation
¶
Overview ¶
Package tui implements Atelier's Bubble Tea TUI.
The TUI is a two-pane layout (left=variable list, right=editor) with a status pane at the bottom (SPEC §7, ADR-0006). The top-level Model owns the wrapper.State and routes input to the active editor based on the selected variable's type.
Index ¶
- func BuildPlanTree(plan *tfjson.Plan) *planNode
- func BuildStateTree(s *state.State) *planNode
- func PlanSummary(plan *tfjson.Plan) string
- type Applier
- type AttributeDiffLine
- type CheckWarning
- type Editor
- type EditorWithCursor
- type EditorWithValue
- type Model
- func (m *Model) ActiveModuleState() *wrapper.State
- func (m *Model) AddModule(state *wrapper.State, name string)
- func (m *Model) AddModuleEntry(e ModuleEntry)
- func (m *Model) Init() tea.Cmd
- func (m *Model) SaveIfDirty() error
- func (m *Model) SelectedPlanChange() *tfjson.ResourceChange
- func (m *Model) SelectedVariable() *tfvars.Variable
- func (m *Model) SetTFState(s *state.State)
- func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd)
- func (m *Model) View() string
- type ModuleEntry
- type Planner
- type RefSwitchResult
- type RefSwitcher
- type RefUnresolvedInfo
- type TfexecPlanner
- type Validator
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildPlanTree ¶
BuildPlanTree groups a parsed plan's ResourceChanges into a (module → type → resource) tree. No-op changes are filtered out (consistent with `terraform plan` itself). Public for tests; the TUI doesn't import it from outside.
func BuildStateTree ¶
BuildStateTree builds a plan-tree-compatible structure from the terraform state, grouped by module → type → resource. This allows navigating state resources using the same tree UI when the plan has no changes.
func PlanSummary ¶
PlanSummary returns the standard one-line counts header.
Types ¶
type Applier ¶
type Applier interface {
// ApplyCmd returns a command that applies the most recent saved plan file
// with the terminal handed back to terraform, so its own output and
// approval prompt reach the user unmediated (ADR-0052).
ApplyCmd() tea.Cmd
}
Applier is the narrow interface the TUI needs to apply a saved plan. Separated from Planner so the capability can be independently stubbed or disabled.
type AttributeDiffLine ¶
type AttributeDiffLine struct {
Marker string // "+", "-", "~"
Key string
Before string // empty for create
After string // empty for delete
}
AttributeDiffLine is one rendered diff line in the right-pane attribute diff view: a key, an action indicator, and one or two value renderings.
func AttributeDiff ¶
func AttributeDiff(rc *tfjson.ResourceChange) []AttributeDiffLine
AttributeDiff renders the attribute-level diff for a single resource change. Sensitive attributes are masked as "<sensitive>".
Behaviour by action:
- create: every After attribute as `+`
- update: keys whose values differ → `~`; new keys → `+`; removed keys → `-`
- delete: every Before attribute as `-`
- replace (delete+create): show like update (forces a clear visual signal)
- read / no-op: empty result
func (AttributeDiffLine) String ¶
func (l AttributeDiffLine) String() string
String renders the line per the convention SPEC §7.4 implies.
type CheckWarning ¶ added in v0.1.0
type CheckWarning struct {
// Address is the display address of the check block, e.g.
// "check.grafana_storage_directives".
Address string
// Message is the error_message the check author wrote.
Message string
}
CheckWarning is one failed `check` block assertion, distilled from the plan's Checks section into just the two things the UI needs: a display address (e.g. "check.grafana_storage_directives") and the author's error_message.
Only `check` blocks are surfaced here. Resource/output pre- and post-conditions (the other CheckKinds) fail the plan itself and are therefore already carried by the plan-error path; check blocks are unique in that they degrade to advisory warnings that never block plan or apply, so they need a dedicated, non-error surface.
func FailedChecks ¶ added in v0.1.0
func FailedChecks(plan *tfjson.Plan) []CheckWarning
FailedChecks extracts the failed `check` block assertions from a parsed plan. It reads the machine-readable Checks section that `terraform show -json` emits, so it never scrapes human-readable stdout and never drifts from Terraform's own semantics.
Only checks with Kind == "check" and Status == "fail" are returned; passing checks, resource/output conditions, and unknown/errored checks are skipped. The result is deterministic (input order preserved) and nil when the plan has no failed check blocks.
type EditorWithCursor ¶
EditorWithCursor is an editor that reports its logical cursor line (0-based) so the right-pane scroll can follow the cursor.
type EditorWithValue ¶
EditorWithValue is an editor that can report its current value back to the top-level model (so edits flow into state.Values).
type Model ¶
type Model struct {
State *wrapper.State
// Modules holds all module states for multi-module wrappers. When
// len(Modules) > 1, the left pane shows section headers to group
// variables by module. When empty or len==1, behaves as before.
Modules []ModuleEntry
// Module display info shown in the status bar.
ModuleName string
LiteralRef string
ResolvedSHA string
SourceURL string
ManifestPath string
// WrapperDir is the directory containing main.tf and terraform.tfstate.
// Used for reloading state after apply.
WrapperDir string
// Planner runs `terraform plan` asynchronously when the user presses P.
// May be nil (e.g. in tests or read-only contexts); the P key just
// produces a friendly status message in that case.
Planner Planner
// Applier runs `terraform apply` when the user presses A from the plan
// view. May be nil; the A key is hidden if unset.
Applier Applier
// Validator runs `terraform validate` after edits (debounced). May be
// nil; validation is skipped if unset.
Validator Validator
// RefSwitcher handles the backend logic of switching module refs.
// May be nil (e.g., local source wrappers where ref switching is N/A).
RefSwitcher RefSwitcher
// RefUnresolved, when non-nil, marks the primary module as opened with a
// ref that no longer resolves on the remote (e.g. the branch was deleted).
// The TUI auto-opens the ref-switch modal on startup and shows a banner
// explaining why the variable schema is unavailable.
RefUnresolved *RefUnresolvedInfo
// contains filtered or unexported fields
}
Model is the top-level TUI model.
func (*Model) ActiveModuleState ¶
ActiveModuleState returns the wrapper.State for the variable currently under the cursor. Falls back to the primary State.
func (*Model) AddModule ¶
AddModule appends an additional module to the TUI. Variables from all modules are shown in the left pane, grouped under section headers when there is more than one module.
func (*Model) AddModuleEntry ¶
func (m *Model) AddModuleEntry(e ModuleEntry)
AddModuleEntry appends a fully-populated module entry (including its ref identity and per-module switcher) to the session.
func (*Model) SaveIfDirty ¶
SaveIfDirty writes the state to disk if there were pending edits. Returns the error from the write, or nil. Exposed so the runtime layer (cmd) can run a final flush before exit.
func (*Model) SelectedPlanChange ¶
func (m *Model) SelectedPlanChange() *tfjson.ResourceChange
SelectedPlanChange returns the resource_change under the plan cursor, or nil when the cursor is on a non-leaf row (module/type) or no plan exists.
func (*Model) SelectedVariable ¶
SelectedVariable returns the variable currently under the cursor, or nil if out of range.
func (*Model) SetTFState ¶
SetTFState sets the parsed terraform state for display in the plan view.
type ModuleEntry ¶
type ModuleEntry struct {
State *wrapper.State
Name string // display name (typically the module block name)
// Ref identity for THIS specific module. Populated for git-sourced
// modules so each module can be ref-switched independently.
SourceURL string
Ref string
ResolvedSHA string
// Switcher performs the ref switch for this module. Nil for local
// sources (no remote to switch); the R key is a no-op for those.
Switcher RefSwitcher
}
ModuleEntry represents one module in a multi-module wrapper session.
type Planner ¶
type Planner interface {
// EnsureInit runs `terraform init` once if needed. Subsequent calls are
// fast no-ops.
EnsureInit(ctx context.Context) error
// Plan runs `terraform plan -out=<tmp>; terraform show -json <tmp>` and
// returns the parsed JSON plan.
Plan(ctx context.Context) (*tfjson.Plan, error)
}
Planner is the narrow interface the TUI needs from a terraform executor. Defined here (rather than depending on tfexec.Terraform directly) so tests can substitute a stub.
type RefSwitchResult ¶
type RefSwitchResult struct {
State *wrapper.State
ResolvedSHA string
LiteralRef string
// OrphanedVars lists variable names that had user-set values in the
// previous state but no longer exist in the module at the new ref.
OrphanedVars []string
// NewVars lists variables added in the new ref that were not present
// in the previous state.
NewVars []tfvars.Variable
// InitIncomplete is true when the post-switch `terraform init -upgrade`
// did not finish cleanly. The switch still succeeds: the module is
// fetched (module installation precedes config validation) and the new
// schema is shown. The most common cause — the new ref added a required
// variable that isn't filled yet — is detected and phrased by the TUI
// from its own state, so this flag only needs to convey the bare signal.
// The planner re-runs init -upgrade on the next plan, and
// `terraform validate` surfaces the specific diagnostics in the meantime.
InitIncomplete bool
}
RefSwitchResult is returned by SwitchRef with the new state, any orphaned variable names (present in old state but absent in new module), and the resolved SHA.
type RefSwitcher ¶
type RefSwitcher interface {
// SwitchRef re-clones the module at the new ref, re-parses variables,
// runs terraform init -upgrade, and returns the updated state. The
// caller is responsible for merging user overrides from the old state.
SwitchRef(ctx context.Context, newRef string) (*RefSwitchResult, error)
// ListRefs returns the human-friendly ref names (branches and tags) the
// module's remote currently has, for presentation in the ref-switch
// modal. It is the source of truth for the "available refs" hint so the
// list is never stale — including after an in-session switch fails or a
// ref is deleted mid-session. Returns an error when the remote can't be
// reached; callers treat that as non-fatal (the input stays free-text).
ListRefs(ctx context.Context) ([]string, error)
}
RefSwitcher is the narrow interface the TUI needs to switch the module ref. Defined as an interface so tests can substitute a stub.
type RefUnresolvedInfo ¶
RefUnresolvedInfo mirrors bootstrap.RefUnresolved for the TUI layer: it records why the wrapper opened without a resolvable ref so the model can auto-open the switch modal, seed the hint, and render an explanatory banner.
type TfexecPlanner ¶
type TfexecPlanner struct {
Tf *tfexec.Terraform
WrapperDir string
// contains filtered or unexported fields
}
TfexecPlanner is the production Planner: it shells out via terraform-exec against a wrapper directory.
func (*TfexecPlanner) ApplyCmd ¶ added in v0.23.1
func (p *TfexecPlanner) ApplyCmd() tea.Cmd
ApplyCmd applies the cached plan file with the terminal handed back to terraform: tea.ExecProcess releases the alt-screen so terraform prints its own progress. The plan was already reviewed in the plan tree and pressing A is the confirmation, so the flags match what the plan-file path has always passed rather than prompting a second time.
func (*TfexecPlanner) EnsureInit ¶
func (p *TfexecPlanner) EnsureInit(ctx context.Context) error
EnsureInit runs `terraform init` if modules have not been installed in the wrapper. The check looks for .terraform/modules/modules.json which Terraform writes when module sources are fetched. This catches the case where .terraform/ exists (from provider init) but the module block's source was added or changed since the last init.
func (*TfexecPlanner) Plan ¶
Plan runs the plan and parses the resulting plan file. The temporary plan file lives under .atelier/cache/ so it doesn't pollute the wrapper root and is regenerable.
func (*TfexecPlanner) ResetInit ¶
func (p *TfexecPlanner) ResetInit()
ResetInit clears the cached init state so the next EnsureInit call will run init -upgrade. Called after a ref switch rewrites the module source. An init already in flight is invalidated rather than cancelled: its result described the old module source, so it must not mark the wrapper ready.
func (*TfexecPlanner) Validate ¶
func (p *TfexecPlanner) Validate(ctx context.Context) (*tfjson.ValidateOutput, error)
Validate runs `terraform validate -json` against the wrapper directory.
type Validator ¶
type Validator interface {
// Validate runs `terraform validate -json` and returns diagnostics.
Validate(ctx context.Context) (*tfjson.ValidateOutput, error)
}
Validator is the narrow interface the TUI needs to run `terraform validate`. Separated from Planner so the capability can be independently stubbed.