render

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package render turns a snapshot.Snapshot into text. Every renderer is a pure function of (Snapshot, Options): no clock, no environment, no Kubernetes, no globals — which is exactly what makes the golden tests in this package a complete specification of wfctl's output.

Two conventions run through every renderer, because a fleet view that cannot tell "nothing" from "unknown" is worse than no view at all:

  • "-" is an absent value: a gate has no source, a settled node has no pending age.
  • "?" is an unproven one: this snapshot cannot say. A table that prints any "?" also prints a footnote naming the fix.

Colour is decided by the caller, never here: NO_COLOR, --no-color and TTY detection all belong to the CLI, and a renderer that sniffed the environment could not be golden-tested.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DOT

func DOT(w io.Writer, s *snapshot.Snapshot) error

DOT writes the graph as Graphviz source, for `-o dot`.

State is carried by colour, role by shape, and the two exceptional structures — a hold and a cycle — by border treatments, so a rendered diagram answers "what is stuck, and why" without a legend lookup. The dashed red edges are the engine's own attribution: each points from a blocked node's blocker to the node it is blocking.

func Explain

func Explain(w io.Writer, s *snapshot.Snapshot, ref adapter.NodeRef, o Options) error

Explain walks a node's attribution chain to its root cause and names the fix.

The walk is the engine's own attribution read backwards: each blocked node names its nearest unsettled ancestor — or, for SharedSourceBlocked, the sibling that shares its source — so following those links terminates at the one node whose state actually has to change. SelfHeld and GraphCycle name no ancestor and end the walk where they are; a visited set makes the walk total even against an attribution chain that loops.

func Graph

func Graph(w io.Writer, s *snapshot.Snapshot, o Options) error

Graph renders the default graph view: dependsOn depth layers, or "waves".

Every node in a wave can advance concurrently with every other node in it, which is the property the layering exists to show: the frontier is wide, not a queue. Anything that never layered is listed apart, because a cycle has no depth and inventing one would suggest an order the engine explicitly refuses to assume.

func History

func History(w io.Writer, events []eventsv1.Event, o Options) error

History renders one row per event, for `wfctl history`.

TIME is how long ago the event last occurred — kubectl's own "LAST SEEN" convention, collapsed to a single column — anchored on snapshot.EventTime, which already resolves eventTime, the series' last-observed heartbeat, or the deprecated firstTimestamp in that order. COUNT is the series' occurrence count, the recorder's own way of collapsing a repeated event onto a single object instead of a new one each time. NOTE is printed verbatim except for characters a table cell cannot carry.

func JSON

func JSON(w io.Writer, v any) error

JSON writes v as indented JSON with a trailing newline.

A Snapshot round-trips through this and back through snapshot.FileSource unchanged — `wfctl snapshot > f.json` then `--from f.json` is the contract the golden fixtures themselves rely on.

func Mermaid

func Mermaid(w io.Writer, s *snapshot.Snapshot) error

Mermaid writes the graph as Mermaid flowchart source, for `-o mermaid`.

The encoding is the DOT one restated for a renderer that lives in a pull request or a runbook: colour is state, a hexagon is a gate, a dashed border is a hold, a thick border is a cycle, and the dashed red links are the engine's blocked-by attribution. Node ids are positional (n0, n1, …) because Mermaid ids may not contain the slashes a node reference is made of; the label carries the real name.

func Nodes

func Nodes(w io.Writer, s *snapshot.Snapshot, o Options) error

Nodes renders one row per evaluated node.

The row is the fleet's per-node truth in the order an operator asks for it: what it is, what state it reached, and — when it did not reach Settled — who is holding it up. PIN and OBSERVED are short SHAs because the question they answer is "are these the same?", which seven characters settle.

func Source

func Source(w io.Writer, s *snapshot.Snapshot, name string, o Options) error

Source renders one managed GitRepository in detail.

Unlike the table, this view prints full SHAs and the provenance annotations verbatim: it is what an operator reads before writing to the object, and before a write the exact value matters.

func Sources

func Sources(w io.Writer, s *snapshot.Snapshot, o Options) error

Sources renders one row per managed GitRepository.

This is the pin ledger: the pin, what the ref actually advertises, and who owns the field. `-o wide` adds the provenance of the last advance, which is the durable record, unlike events, which the apiserver eventually expires.

func Status

func Status(w io.Writer, s *snapshot.Snapshot, o Options) error

Status renders the fleet summary: the Wavefront itself, the phase and counts, the structural verdict, the exceptional-state lists, and the diagnostics.

Under the derive origin every count is printed twice — what the controller published, and what re-deriving now says — with the disagreements marked. Under any other origin there is only one number to print, and pretending otherwise would imply a comparison that was never made.

func Tree

func Tree(w io.Writer, s *snapshot.Snapshot, o Options) error

Tree renders the graph as rooted trees, for `wfctl graph --tree`.

Roots are wave 0 — the nodes nothing gates — and each is expanded depth first. The DAG is not a tree, so a node with several parents is expanded under the first one that reaches it and referred back to everywhere else: printing its subtree once per parent would multiply a shared platform component across the whole fleet and bury the shape it is meant to show.

func YAML

func YAML(w io.Writer, v any) error

YAML writes v as YAML.

sigs.k8s.io/yaml goes through the JSON tags, so `-o yaml` and `-o json` describe the same document with the same field names — anything else would make the two outputs disagree about a schema they share.

Types

type Options

type Options struct {
	// Wide selects the extra columns of `-o wide`.
	Wide bool
	// Palette colours state cells; the zero value is colourless.
	Palette Palette
	// Now anchors every rendered age. Zero means time.Now — the golden tests
	// set it so ages are stable.
	Now time.Time
}

Options is every knob the renderers share.

type Palette

type Palette struct {
	Enabled bool
}

Palette colours terminal output.

Whether colour is wanted is the CLI's decision (NO_COLOR, --no-color, term.IsTerminal): render never inspects the environment, so its output is a pure function of its inputs and can be golden-tested. The zero Palette is colourless, which is what a test, a pipe, and a file all want.

func NewPalette

func NewPalette(enabled bool) Palette

NewPalette returns a Palette that colours only when enabled.

func (Palette) Dim

func (p Palette) Dim(text string) string

Dim renders secondary text — footnotes, absent markers, provenance.

func (Palette) Held

func (p Palette) Held(text string) string

Held dims a cell belonging to a held source: a hold is an operator's deliberate act, so it reads as struck out rather than alarming.

func (Palette) State

func (p Palette) State(state string) string

State colours a node state cell.

func (Palette) Warn

func (p Palette) Warn(text string) string

Warn colours a value that disagrees with another, or a diagnostic.

type Table

type Table struct {
	// contains filtered or unexported fields
}

Table is a tabwriter-backed column layout with a header.

Colour is applied *after* layout, never before. text/tabwriter measures a cell by counting its runes (Writer.updateWidth), and bracketing an escape sequence in tabwriter.Escape does not exempt it: endEscape calls updateWidth for exactly that case, so Escape protects a tab or newline from being read as a separator, not a colour from being counted as width. Feeding it coloured cells therefore pads every coloured row by the byte length of its ANSI sequences and skews the whole table.

So rows are held until Flush, laid out from their ANSI-stripped text, and the styled text is substituted back into the finished lines. An uncoloured table takes a fast path through that substitution and its bytes are exactly what the tabwriter produced.

func NewTable

func NewTable(w io.Writer, header ...string) *Table

NewTable starts a table with the given header row.

func (*Table) Flush

func (t *Table) Flush() error

Flush lays the table out and writes it.

func (*Table) Row

func (t *Table) Row(cells ...string)

Row appends one row. A cell may carry ANSI colour; it must not contain a tab or a newline (see sanitize).

Jump to

Keyboard shortcuts

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