Skip to content
Use kx in CI

Use kx in CI

kx diag and kx scan both sweep a namespace and print a table. In a pipeline nothing reads the table, and both would exit 0 whatever they found. Two flags change that. (kx tree and kx top take --json too, for the ownership graph and the usage listing; only the two that produce findings take --fail-on.)

kx diag -A --fail-on critical    # 0 if the cluster is healthy, 2 if not
kx scan -n prod --fail-on high --json

--json

The same analysis as a machine-readable document — every resource swept, healthy ones included, and every CVE behind the severity counts.

kx diag -n prod --json | jq '.resources[] | select(.verdict == "critical") | .name'
kx scan -n prod --json | jq '.images[] | select(.counts.critical > 0)'

kx diag emits one shape whether it was pointed at an index or a namespace, so that first expression reads kx diag 1 --json too — an indexed run is a sweep of one, and counts itself that way.

It is built from the same values the terminal and --html render, so the three views cannot disagree about what is wrong. A sweep serialises every resource regardless of --full: that flag governs how much of a table fits on a screen, and nothing is scrolling past a machine.

The document carries a schemaVersion, because this is a public surface the moment it ships — something will parse it in a pipeline, and a field moving underneath that is worse than one it can check for.

{
  "schemaVersion": 1,
  "namespace": "prod",
  "checked": 12,
  "healthy": 1,
  "resources": [  ]
}

All four commands that emit --json name their subject with the same fields, so a pipeline that reads one does not have to learn a second shape to read the others. A sweep carries namespace, or allNamespaces: true for -A; an indexed run carries kind, name and namespace:

{
  "schemaVersion": 1,
  "kind": "Deployment",
  "name": "api",
  "namespace": "prod",
  "images": [  ]
}

kx tree --json names every node with kind and name rather than the rs/web-7d8f label the terminal draws, carries the same index the tree printed, and always returns a roots list — one entry for an indexed resource or a single namespace, one per namespace for -A. A pod’s containers appear as children with a name and no kind, because a container is part of a pod rather than a resource of its own.

kx top --json reports percentages as numbers, and as null where there is none — a pod with no limit set has no percentage, and 0 would read as idle. Its resource field says whether the listing was pods or nodes, since a pod’s percentage is against its limits and a node’s against its capacity.

Severities are lower case throughout — critical, high, medium, low for image findings, critical, warning, healthy for verdicts — which is exactly what --fail-on accepts, so a value read out of a document can be typed straight back at the gate.

--fail-on

Turns either command into a gate.

CommandAccepted thresholds
kx diagcritical, warning
kx scancritical, high, medium, low

The threshold is inclusive: --fail-on high fails on high and critical. It is validated before the cluster is read, so a typo fails immediately rather than after a sweep has already run.

An image whose scan failed breaches every threshold, for the same reason a missing test is not a passing one: an image kx could not read has not been shown to be clean.

The exit code is 2

Two, not one, and the difference is the point:

CodeMeaning
0The check ran and found nothing at the threshold.
1kx itself failed — no cluster, no scanner, bad flags. The check did not run.
2The check ran and found something.

A pipeline that treated any non-zero as “unhealthy” could not tell a sick cluster from an unreachable one.

Publishing a report and failing on it

--fail-on is independent of how the findings are presented. It applies alongside --json and --html alike, so a job can publish a report and still fail on what is in it:

kx diag -A --fail-on critical --out diag.html

--out writes the page and returns, so the gate runs and the file is there for an artifact step to pick up — and it implies --html on its own, so there is no need to pass both. Plain --html without --out serves the report and blocks until Ctrl-C, which is right at a terminal and wrong in a pipeline — nothing sends Ctrl-C to a CI job, so it would hang until the runner killed it.

kx scan --full --fail-on is refused rather than ignored. --full streams the scanner’s own report, which kx never parses, so the gate would have nothing to read. --json and --full are refused together for the same reason, and kx diag --json --full is refused because a document already carries every resource swept — --full has nothing to add to one.

A whole cluster

kx diag -A --fail-on critical
kx scan -A --fail-on high

kx scan -A resolves every unique image in the cluster and scans each once, two at a time. The bound is memory rather than cores — a scanner unpacks an image and walks every package in it — so a wide sweep is steady rather than fast, and does not thrash a small runner.