docs/cli-refactor/2026-05-25-output-formats-design.md
-o json|yaml|wide output formats for Fission CLI read commandsStatus: Implemented (branch cli-output-formats).
Date: 2026-05-25.
Fission CLI list and describe commands print a single hard-coded human table.
There is no machine-readable output, so scripting against fission means parsing column text — brittle and undocumented.
kubectl solves this with -o json|yaml|wide|name; users expect the same.
The recent dedup work added shared table helpers (util.PrintTable, util.PrintItems, util.ConditionStatus), which gives a natural seam to introduce a format-aware printer without rewriting each command.
sigs.k8s.io/yaml is already a dependency (used by spec SpecDry/crdToYaml), so object→YAML is a solved problem.
The -o/--output flag is already used by download commands (pkg getsrc, pkg getdeploy, archive download, support dump) to mean "output file path".
Those are different commands from the read/list commands, so a format-selector -o on the read commands does not collide.
Add -o json, -o yaml, and -o wide to the read commands.
No flag keeps today's exact table output (so #3397's default READY column is preserved).
Purely additive and backward compatible.
In scope (gain -o):
list: function, environment, httptrigger, timetrigger, mqtrigger, kubewatch (watch), canaryconfig, package.fn getmeta, pkg info, canary get, ht get.Out of scope:
-o (pkg getsrc/getdeploy, archive, support dump) — unchanged.fn get (streams raw source bytes), pkg getsrc/getdeploy (archive bytes) — not object views.spec list (its own multi-section format) — could follow later; not in this doc.-o name — deferred (YAGNI for now; easy to add later under the same printer).A new shared flag flag.Output (--output, short -o, type String, default "") registered as an Optional flag on the in-scope commands.
Empty → current table. Valid values: json, yaml, wide. Unknown value → error listing the valid set.
Note: a flagkey.Output = "output" key already exists (aliased by PkgOutput/ArchiveOutput/SupportOutput for the download commands).
The implementation reuses that existing flagkey.Output key for the read commands' format flag (flag.Output); the download commands keep their own flag definitions unchanged.
Both can share the literal name output/-o because no single command registers both.
json: marshal the (already-filtered) result. For list → a JSON array of the objects ([]Function); for describe/single → one object. Arrays chosen over a *List wrapper because they are simpler to consume with jq and the commands already work with .Items slices.yaml: the slice marshaled via sigs.k8s.io/yaml as a single YAML sequence (not ----separated documents); a single object for describe.wide: the current table plus extra columns. Minimum new column: AGE (from CreationTimestamp, rendered with duration.HumanDuration). Per-resource extras may be added (e.g. function READY-reason). Column order: the existing columns (including NAMESPACE where present) followed by the wide-only columns, so AGE appears last — PrintObjects simply appends the wide columns to keep the helper generic.Add to pkg/fission-cli/util/output.go:
// OutputFormat is the validated value of -o.
type OutputFormat string
const (
OutputTable OutputFormat = "" // default human table
OutputWide OutputFormat = "wide"
OutputJSON OutputFormat = "json"
OutputYAML OutputFormat = "yaml"
)
// ParseOutputFormat validates the -o value (empty allowed).
func ParseOutputFormat(s string) (OutputFormat, error)
// PrintObjects renders items per the format. For json/yaml it marshals items;
// for table/wide it builds rows via the provided closures. headers/row are the
// default columns; wideHeaders/wideRow add the -o wide columns. A single-object
// describe view passes a one-element slice and marshals as the bare object.
func PrintObjects[T any](
format OutputFormat,
items []T,
headers []string, row func(T) []string,
wideExtra []string, wideRow func(T) []string,
) error
json/yaml marshal items directly (the CRD types already carry the right json tags).
table uses headers+row; wide uses headers+wideExtra and row+wideRow concatenated.
List commands call PrintObjects(format, items, …). Describe commands call PrintStructured(format, obj) (handled bool, err error): for json/yaml it marshals the bare object and returns handled=true; for table/wide it returns handled=false and the command falls back to its existing human rendering.
pkg/fission-cli/util/output.go — printer + format parsing (+ tests).pkg/fission-cli/flag/flag.go, flag/key/key.go — the --output flag for read commands.pkg/fission-cli/cmd/*/command.go — add flag.Output to the in-scope list/describe subcommands.pkg/fission-cli/cmd/*/list.go, getmeta.go, package/info.go, canaryconfig/get.go, httptrigger/get.go — read -o, define wide columns, call the printer.-o) is unchanged, including #3397's READY column and the conditions block in describe.--output is new and optional.-o semantics are untouched.--help.util): ParseOutputFormat valid/invalid; PrintObjects for table/wide/json/yaml on a sample type (assert JSON parses back as an array, YAML contains the field, wide has the extra header).fn list -o json|yaml|wide test via the dummy driver + fake clientset asserting shape.fission fn list -o json | jq '.[].metadata.name', -o yaml, -o wide shows AGE; unknown -o foo errors.-o name, -o jsonpath=…, -o custom-columns=….spec list -o json.-o json *List wrapper (kubectl-style) — revisit if users want kind/apiVersion envelope.