Documentation
¶
Overview ¶
Shells out to ansible-playbook using the ansible.posix.jsonl stdout callback and streams events live into an interactive TUI as they arrive.
Implements the recap section (design-docs/Recap.md): a per-host ansible-style summary line ("hostname : ok=159 changed=94 ...") appended below the tree once a run finishes, expandable into its own non-empty outcome categories, each expandable into the individual tasks that landed in it. Deliberately built entirely on data PlaybookState already tracks (a scan over Plays/Tasks/Hosts) - no aggregate.go changes needed, and no rescued/ignored categories: those aren't derivable per-task from this app's own event consumption (a rescued task reports as an entirely ordinary v2_runner_on_ok, and ignore_errors is never included in the jsonl callback's own emitted per-task JSON - confirmed by reading ansible.posix.jsonl's own source directly), and were explicitly descoped rather than shown as count-only fields with no drill-down to back them up.
Implements the drill-down view's "Resolved" section (design-docs/ Drilldown, Resolved Values.md): a task's own source, re-rendered with its variables filled in, by feeding it back into ansible as a real ansible.builtin.template task rather than reimplementing variable resolution ourselves - ansible's own templating engine, on ansible's own terms, is the only thing that can correctly resolve a value without duplicating its ~14-level variable precedence chain (or reaching into undocumented internals no supported plugin API exposes - confirmed live in the design conversation this follows from that even a custom callback plugin doesn't get handed the VariableManager either).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewLiveTUI ¶
func NewLiveTUI(state *playbook.PlaybookState, playbookName string, isRole bool, procH *runner.ProcHandle, processDone, quitting *atomic.Bool, exitCode *atomic.Int32, sourceIndex source.TaskSourceIndex, knownTags, knownTaskNames, knownPlayNames []string, startExpanded, twoPaneLayout, colorEnabled bool, initialTags, initialSkipTags, initialHosts string, startWithRerunDialog bool, requestRerun func(startAtPlay, startAtTask, tags, skipTags, hosts string), passthroughArgs []string, progH *atomic.Pointer[runner.ProgressTracker], revisitReturn func(), targetPlaybook, targetRole string) (app *tview.Application, applyLive func(playbook.RawEvent))
NewLiveTUI builds an initially-empty list UI and wires it to state's hooks so it grows as events arrive. It does not block — the caller must call app.Run() and feed events through applyLive.
procH holds ansible-playbook's current process, used so Ctrl-C/q can forward SIGINT to it while it's still running (tcell's raw mode disables the OS's own Ctrl-C-to-SIGINT delivery, so without this the child would stop receiving the interrupt it used to get for free — see Purpose.md's Ctrl-C decision). A mutable holder rather than a plain *os.Process - unlike everything else this function reads at construction time and never again - because a rerun (Rerun.md, see requestRerun below) points it at a freshly spawned child mid-session; SetInputCapture always reads whichever process is current via procH.Load(). processDone/quitting are shared with the caller: this function only reads processDone and only writes quitting.
initialTags/initialSkipTags/initialHosts pre-fill the re-run dialog's own Tags/Skip tags/Hosts fields the first time it's opened (and every time after, until the user edits them - the dialog's own fields, once opened, keep whatever the user last left in them across repeated 'r' presses) - the --tags/--skip-tags/--limit values this process was itself invoked with, parsed out by main.go via ParsePassthroughArgs (Rerun.md's "if tags were already specified in the previous run... pre-filled").
requestRerun is called once the re-run dialog is confirmed (Enter), to start a new generation with the dialog's own fields: startAtPlay (empty unless a top-level play was chosen - design-docs/StartWithPlay.md; spawns against a trimmed, temporary copy of the playbook with every play before it dropped, rather than editing the real file), startAtTask (empty for a whole-playbook re-run, otherwise the task name to pass as --start-at-task, applied against whatever startAtPlay already narrowed the file down to - see openRerunDialog below for how it's pre-filled), and the edited tags/skipTags/hosts. main.go's implementation resets processDone/exitCode/ state, records the new invocation into .tangsible's history, and spawns a fresh ansible-playbook invocation; this function's own job is only to reset its own view state (expanded/currentID/following/the freeze latches) and restart the heartbeat ticker to match - see submitRerun below.
sourceIndex (source.go) backs the output drill-down view's TASK: section (see formatHostOutput) - a lookup miss (any task whose path wasn't found while building the index) just means no TASK: section for that entry, never an error.
knownTags (source.go's BuildTaskSourceIndex, its own second return value) is every literal tags: value found in the playbook/role tree, unioned with source.ReservedTagNames - the re-run dialog's Tags/Skip tags fields' own autocomplete candidate list (design-docs/Autocomplete.md). knownTaskNames (BuildTaskSourceIndex's third return value) is the same idea for the "Start with task" field - every literal task name: found the same static way, deliberately not sourced from state.AllTasks (see BuildTaskSourceIndex's own doc comment for why). knownPlayNames (source.ListTopLevelPlayNames) is the same idea again for the "Start with play" field (design-docs/StartWithPlay.md) - every named top-level play, in file order; v1 deliberately doesn't follow import_playbook, so a play defined in an included file simply isn't a candidate. Hosts need no equivalent parameter: the Limit hosts field's own candidates come straight from state.AllHosts, already in scope.
startExpanded governs the very first task row's own initial expand/collapse state (`.tangsible`'s general.default_tree_state - see DefaultTreeExpanded, resolve.go), read once by main.go before construction. Every task after the first inherits whatever the previous task's current expand state is at the moment it's added - see state.OnTaskAdded below - so this value only ever actually governs one row per generation (the very first task added since the last Reset()).
startWithRerunDialog is true only for the "rerun" Verb's own startup (Rerun.md): no ansible-playbook invocation exists yet at all - not even a first one in flight, unlike every other case this function handles - so the re-run dialog opens immediately instead of waiting for 'r', and processDone is expected to already be true when this is called (main.go sets it before constructing the TUI): accurate ("no generation is currently in flight"), and what safely unlocks the dialog-opening/ quit-outright behavior the rest of this function already has for a frozen run - see everStarted below for the one place that distinction actually matters once frozen means "genuinely nothing has run yet" rather than "a run finished." passthroughArgs is this session's own current-generation passthrough args (main.go's originalArgs.Rest - importantly -i/-e, never -l/--limit, which ParsePassthroughArgs already extracts separately) - threaded through only so showOutput's own resolveTaskValues calls (see design-docs/Drilldown, Resolved Values.md) see the same inventory/extra-vars context the real run did. Not used for anything else in this function.
colorEnabled is general.color's own resolved value (ColorEnabledByUser, resolve.go), read once by main.go before construction - one of three independent inputs (alongside the terminal's own detected color capability and the NO_COLOR environment variable) combined below into useColor, design-docs/Morehosts.md's own gate on whether the collapsed task row's per-host summary may render in color at all. revisitReturn, if non-nil, marks this session as design-docs/Revisit.md's "revisit" Verb showing a replayed (historical) run rather than a live run/rerun/role session: state/processDone/exitCode are already fully populated by the time this constructor is called (see revisit.go), chrome switches to ReplayBarStyle for as long as revisitActive stays true, and pressing Esc at the bare tree level (not in a dialog, not viewing output - nothing else has ever claimed that key there) calls revisitReturn, which is expected to stop app.Run() and let the caller show the run list again. nil for every other Verb - Esc keeps doing nothing at that level, exactly as before this existed.
targetPlaybook/targetRole (exactly one non-empty, mirroring AppendInvocation's own playbook/role parameters) is this session's own target identity, exactly as recorded in state.toml - distinct from playbookName, which is display-only (main.go passes it filepath.Base(playbook), not the full path state.toml itself keys on). Needed for design-docs/Diff.md's own 'd' key, to look up this session's own history entry and filter comparison candidates against it (RunDiffFlow, diff.go).
Types ¶
This section is empty.