Skip to content

feat(cli): describe an unsubmitted manifest with -f - #328

Draft
rogirun wants to merge 4 commits into
feat/cli-describe-commandfrom
feat/cli-describe-file-mode
Draft

rogirun wants to merge 4 commits into
feat/cli-describe-commandfrom
feat/cli-describe-file-mode

Conversation

@rogirun

@rogirun rogirun commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds -f, building the same view from a manifest alone with no cluster and no pods. The no-definition failure is emitted as a parseable object on stdout under a machine format, because an agent's fallback there differs from its fallback for any other failure.

Part of a seven-PR stack implementing kli describe (#206). Targets feat/cli-describe-command, which must merge first.

Related issue(s)

Refs #206

Checklist

  • All commits are signed off with DCO (git commit -s)
  • New/modified files have SPDX license and copyright headers
  • Documentation updated (if applicable)
  • Tests pass (make check)
  • No proprietary or internal information included

🤖 Generated with Claude Code

@rogirun
rogirun force-pushed the feat/cli-describe-file-mode branch from a9e1563 to c17c138 Compare September 6, 2026 12:59
@rogirun
rogirun force-pushed the feat/cli-describe-command branch 2 times, most recently from 1ec50de to 8209d30 Compare September 7, 2026 10:43
@rogirun
rogirun force-pushed the feat/cli-describe-file-mode branch from c17c138 to a7e281c Compare September 7, 2026 10:43
Every describe section - header, tree, phase, resources - has to agree, and a
machine consumer has to read the same picture a human does. One struct is what
guarantees that, so DescribeView carries the whole workload and each rendering
is a projection of it.

Values are typed: replicas are {desired, current, ready} numbers rather than a
"3/4" a consumer re-parses and that breaks silently when the rendering changes.
An unscheduled pod has a null node, not an empty string standing in for one.

ResolveDescribe builds the spec-only tree, then narrows the pods handed to it
top-down through the definition's own PodSelector: ComponentTypeSelector picks
the role, ComponentInstanceSelector and ReplicaSelector split a multi-instance
component into one child per instance. Passing no pods yields the same struct
with every live field empty, which is what file mode will render.

Requested resources come from the spec times the desired scale, so they do not
move when a replica is missing; cpu is millicores and memory bytes, so a
consumer sums integers. tree.Build drops the root, so a root-hosted pod
template (Deployment, Job, StatefulSet, Pod) is rebuilt from the root component
and goes through the same path. An intermediate component the workload never
populates, such as a Deployment's ReplicaSet, is collapsed rather than rendered
as an empty row.

Refs #206

Signed-off-by: Roee Gil <roee.gil@run.ai>
Render the DescribeView as the four sections the command promises: a header
naming the workload and the definition that resolved it, the component tree
with its pod rows, the normalized phase, and a resource breakdown per component
with the workload total last.

The tree and the resource table go through one tab writer each, so component
rows and pod rows share a column grid without hand-computed widths.

--pod-limit defaults to showing every pod, the way kubectl-tree renders every
descendant: a hidden pod is the one a reader most needs. When a limit is set,
unhealthy pods sort first so truncation can never hide a failing pod, and the
note says how many were hidden and how many unhealthy ones survived.

The machine formats emit the view itself rather than the items/count envelope
the list commands carry: an envelope says nothing about a single workload and
costs every consumer an items[0] hop. RenderOne carries that in the generic
renderer, so json and yaml stay one code path.

Refs #206

Signed-off-by: Roee Gil <roee.gil@run.ai>
kli describe TYPE/NAME reads one workload in full: the component tree with its
live pods, the normalized phase, and the requested resources per component. It
is the describe half of the kubectl-style get/describe split, and the entry
point an agent uses when it needs the whole picture of one workload.

Argument parsing is shared with get, so both commands accept TYPE/NAME and the
two-token TYPE NAME form and reject the same mistakes; describe additionally
requires the NAME, and says so naming both forms rather than only the one the
caller did not use. Phase 1 keeps the kind mandatory, and the parsing leaves
the bare-NAME slot open for the cross-kind search that follows.

Pods are listed once from the object's own namespace and scoped by ownership
before the definition's selectors place them, so a neighbouring workload of the
same type never appears in the tree. -o wide is rejected at parse time: one
workload has no extra columns to widen into.

Refs #206

Signed-off-by: Roee Gil <roee.gil@run.ai>
kli describe -f jobset.yaml answers what a workload would look like before it
is submitted: the same view, built from the manifest alone, with no cluster
needed. The kind comes from the manifest, so -f accepts no TYPE/NAME and says
so when given one.

File mode and live mode share one struct rather than growing a second shape:
the structure and desired scale are real, and everything live - pods, ready
counts, phase - is left empty and marked as absent. The tree reports the
desired scale instead of a "0/9 ready" that would read as nine pods failing to
start.

The no-definition failure is emitted as a parseable object on stdout when a
machine format was asked for, and stays the plain message on stderr for a
reader. That case alone gets the treatment: an agent's fallback for a type
Karta does not cover - inspect the object raw, or author a definition - is
unlike its fallback for any other failure, so it is worth more than a distinct
exit code.

Refs #206

Signed-off-by: Roee Gil <roee.gil@run.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant