Reference CLI
diff
Compare a cluster build with the live cluster, a previous build, or a saved manifest directory, package by package.
kix diff evaluates and builds a cluster, then compares the result with an
old side: the live cluster by default, or a directory passed with --from.
It only reads, so it needs get and list on the cluster, or no cluster at
all with --from. It never changes the cluster.
A read cannot see what shows only on the write path: admission webhooks and
policies, field ownership conflicts, and what the deploy itself refuses. To
preview a deploy you are about to run, use
kix plan, which sends each write as a
server-side dry run.
This page is hand-maintained. Check cli/kix-cli/src/cli.rs and
cli/kix-cli/src/commands/diff.rs when in doubt.
Synopsis
Section titled “Synopsis”kix diff <CLUSTER> [--from <DIR>] [-o text|json|yaml|markdown] [--detail plain|fields|manifests] [--flake <FLAKE>] [--context <CONTEXT>]| Argument or flag | Meaning |
|---|---|
<CLUSTER> | Cluster name to evaluate and build from the flake. This is the new side of the comparison. |
--from <DIR> | Use a directory as the old side instead of the live cluster. See Old side. |
-o, --output | text (default), json, yaml (the JSON document as YAML), or markdown for pull request comments. |
--detail <LEVEL> | How much json and yaml say about each change: plain (default) names each changed resource and counts its changed fields, fields also lists each field change, and manifests also includes the old and new manifest of each changed, added, or removed resource. Text and Markdown are the same at every level. |
--flake <FLAKE> | Flake to evaluate. Defaults to the current directory. |
--context <CONTEXT> | Kubeconfig context for the live cluster. Ignored with --from. |
Old side
Section titled “Old side”| Old side | How to select it | Needs cluster access |
|---|---|---|
| Live cluster | Omit --from | Yes |
| Previous build | --from <DIR> where <DIR> contains kix-packages.json, such as the result of nix build .#cluster-<name>-activation | No |
| Saved manifests | --from <DIR> where <DIR> holds YAML written by kix snapshot save or kix export --for audit | No |
For the live cluster, Kix reads only the fields it applied to each managed
resource. Kubernetes records which tool set each field through server-side
apply, and Kix keeps its own fields and drops the rest: server defaults,
status, fields set by controllers or kubectl, and the bookkeeping
annotations Kix writes after every apply. A cluster that matches its build
reports no differences.
The live side holds only the named cluster’s resources: those whose
kix.run/activations annotation names one of its Activation records. When the
Kubernetes cluster also runs other Kix clusters, their resources are not
reported as removals, and stderr says how many were skipped per cluster. A
resource the build drops that another Kix cluster’s current build still
contains is left out too, since kix deploy --prune keeps it.
Against a live cluster, kix diff also prints an error for each build
resource whose kind the cluster serves as namespaced but whose manifest has
no metadata.namespace. kix deploy refuses to apply a build that contains
one.
A manifest directory must carry the Kix annotations that name each resource’s
package and identity hash. The default kix export mode strips them, so use
--for audit when the old side comes from an export.
Every old side goes through the same comparison. The live cluster and a snapshot of it produce the same report.
What is compared
Section titled “What is compared”The report is organised by package. Each package lands in one group:
| Group | Meaning |
|---|---|
| added | The package is in the build but not on the old side |
| removed | The package is on the old side but not in the build |
| changed | The package exists on both sides and at least one resource differs |
| unchanged | Every resource in the package matches |
Within a changed package, each resource is reported as one of:
| Resource line | Meaning |
|---|---|
+ added or - removed | The resource exists on one side only |
~ modified | The content differs. Text output shows each differing path with a unified diff of the values. |
~ via dependency | Only the identity hash changed, because a dependency of this resource changed. The content is the same. Text output marks these “via dependency”; JSON and Markdown call them dep-affected. |
Two more sections follow the packages:
- Cluster-level resources groups resources that belong to no package, such
as namespaces and custom resource definitions. Text output labels the
group
cluster-level resources; JSON uses the key_cluster. - Activation shows the old and new activation record names when they differ. A build whose content matches the old side keeps the same record name, so this line appears only alongside other changes.
When a stateful resource appears to have moved between packages or names, the report adds a “Stateful migrations detected” section. It is informational; Kix does not migrate data.
Labels and drift
Section titled “Labels and drift”When the deployed build’s manifests are on this machine, each field change
is labelled intended (the deployed and new builds differ here),
reverts_drift (the live object differs from the deployed build and the
deploy puts the deployed value back), or drift_kept (the live object
differs, and the deploy leaves the object alone because its identity hash
did not change). kix plan
defines them in full. With --from <build> every change is intended.
Without the deployed build nothing is labelled, and the drift section is not
checked.
A Drift section lists each field where the live object differs from the
deployed build, with its live and deployed values, the field managers that
own it live, and whether the deploy reverts it. kix deploy --reconcile
reverts the fields the deploy would otherwise leave alone.
Output formats
Section titled “Output formats”A summary line, one block per namespace with each package’s status and
resource lines, the cluster-level group, the activation line, then either the
migration section or No differences found. A field change that puts back a
drifted field is tagged (reverts drift), and one the deploy leaves alone is
tagged (drift; deploy leaves this object alone). The Drift section follows
when any field drifted. When there are changes, the output ends with a note
that kix diff cannot see admission webhooks, field ownership conflicts, or
what the deploy refuses. Colour
follows --no-color and the NO_COLOR environment variable.
JSON and YAML
Section titled “JSON and YAML”The Plan document kix plan
prints, with JSON Schema cli/kix-cli/schema/plan.schema.json. That page
describes the top-level keys, the checked-section shape, and the
not_checked reasons. For kix diff:
| Key | Value from kix diff |
|---|---|
made_by | live_read against the cluster or a snapshot; builds with --from <build> |
build | The new build, as for kix plan |
deployed | The old side’s Activation record: the cluster’s current record, the --from build’s record, or the record in a --from snapshot. null when there is none |
flags | null: a diff plans no deploy |
migrations | Checked: the stateful migrations the comparison found |
drift | Checked against the live cluster when the deployed build is on this machine. Otherwise not_checked: deployed_build_unavailable for a snapshot or a missing deployed build, builds_only with --from <build> |
conflicts, admission, prune | not_checked, reason read_only (or builds_only with --from <build>) |
readiness | not_checked, reason not_previewable |
not_checked | Always empty |
packages holds the package-grouped changes:
| Key | Content |
|---|---|
packages.summary | Counts of added, removed, changed, and unchanged packages |
packages.added, packages.removed | Arrays of { key, version, resources, kept_resources, manifests }. resources counts every resource in the package; kept_resources lists the resources that stay on the cluster: Namespaces, CustomResourceDefinitions, and objects another Kix cluster’s current build contains (always empty on added). manifests is present at --detail manifests: the new manifest of each resource of an added package, or the old manifest of each resource a removed package deletes |
packages.changed | Array of package changes: key, old_version, new_version, added_resources, removed_resources, kept_resources, modified_resources, dep_affected_resources, and at --detail manifests manifests. A resource that left the build and stays on the cluster is listed in kept_resources, not removed_resources |
packages.changed[].manifests | Object keyed by resource, each { old, new }, for every modified, added, and removed resource. A side is absent when the resource does not exist there |
packages.changed[].modified_resources | Array of { resource, diff_count, changes }. changes is present at --detail fields and --detail manifests. Each entry of changes is { path, change, old, new, labels }; change is added, removed, or changed, old or new is absent when that side has no value, and labels is absent when the deployed build is not on this machine |
packages.cluster_level | The same shape as one changed package, with key _cluster, or null |
packages.activation | { old, new } record names, or null when unchanged |
packages.unchanged | Number of unchanged packages |
Stateful migrations are under the top-level migrations section, each
{ slot, source, target, reason }.
A Secret value, or a hash of one, reads (secret, not shown), or
(secret, changed, not shown) on the new side when the value changes. A
field value larger than 16 KiB, or a manifest larger than 256 KiB, reads
(N bytes, not shown).
Resource identifiers have the form Kind/name@namespace. Package keys have
the form namespace/name.
Markdown
Section titled “Markdown”GitHub-flavoured Markdown: a plan summary, a <details> block per changed
package, the cluster-level and activation sections, and the migration section
when present.
Exit status
Section titled “Exit status”| Code | Meaning |
|---|---|
0 | No differences |
2 | At least one difference, or a stateful migration was detected |
1 | Evaluation, build, or cluster error |
The status is the same for every output format, so a CI job can write Markdown to a file and still branch on the result.