# Save and diff snapshots for offline CI

Use this setup when pull request jobs must not hold cluster credentials but
reviewers still want to see what a change does to the cluster. The deploy job
saves a snapshot after each deploy. Pull request jobs diff their build against
that snapshot, with no connection to the cluster.

This guide assumes a deploy job already runs `kix deploy` with cluster access,
and that you know how to pass a directory between jobs in your CI provider
(an artifact, a cache, or a commit to a snapshots branch).

## Save the snapshot in the deploy job

After a successful deploy, save the cluster and publish the directory:

```sh title="ci/deploy.sh"
set -e
kix deploy production --context "$KIX_CONTEXT"
kix snapshot save "snapshots/production" --context "$KIX_CONTEXT"
# Publish snapshots/production as an artifact, or commit it to the
# snapshots branch, so pull request jobs can download it.
```

The snapshot contains the fields Kix applied to each managed resource,
including the data of any `Secret` Kix manages. Give the artifact the same
access controls as the rendered manifests.

Save on every deploy, not only on the first. A pull request diff against a
stale snapshot reports changes that are already live.

## Diff in the pull request job

Download the latest snapshot for the target cluster, then diff the pull
request's build against it. The job needs the source checkout and Nix, and
nothing from the cluster:

```sh title="ci/pr-diff.sh"
set +e
kix diff production --from snapshots/production -o markdown > kix-diff.md
diff_status=$?
set -e

if [ "$diff_status" -ne 0 ] && [ "$diff_status" -ne 2 ]; then
  exit "$diff_status"
fi

if [ "$diff_status" -eq 2 ]; then
  # Post kix-diff.md as a pull request comment or job summary here.
  # Exit 0 instead if a non-empty diff should not fail the job.
  exit 2
fi

exit 0
```

`kix diff` exits with `2` when it finds differences, so the script captures
the status before deciding what to do with it. Exit `0` after publishing if a
non-empty diff should not fail the job. The exit codes and the Markdown
layout are the same as for a live diff, so the publishing step from
[Generate PR-review Markdown from `kix diff`](/docs/how-to/policy-ci-and-compliance/generate-pr-review-markdown-from-kix-diff/)
works unchanged.

## Check the setup locally

Run the same two commands by hand before wiring them into CI. With the
`how-to-application` example cluster deployed to kind:

<Command {...save} />

<Command {...diffClean} />

The diff reports no differences and exits `0`. Edit a package, rerun the
diff, and confirm the report names the resource you changed and exits `2`.

## What the reviewer sees

The Markdown report lists each changed package with its modified resources,
resources affected only through a dependency, cluster-level resources such as
namespaces, and the activation record names.

The report always contains text. When nothing changed, it contains a
`### kix diff` heading followed by `No package-level changes (N unchanged)`.
Use the captured exit status, rather than the file size, to decide whether to
post the report.

## Troubleshooting

**Every package shows as added and every resource as removed.** The directory
was produced by `kix export` in its default mode, which strips the
annotations Kix needs. Use `kix snapshot save`, or `kix export --for audit`.

**The diff lists resources the pull request did not touch.** The deploy job
did not save after its last deploy, so the snapshot lags the cluster. Re-run
the save and download the new artifact.

:::note[Reference]
See [`kix snapshot`](/docs/reference/cli/snapshot/) for what a snapshot holds
and [`kix diff`](/docs/reference/cli/diff/) for the JSON and Markdown
shapes.
:::

:::tip[Use it in a task]
See [Use snapshots for offline or CI diffs](/docs/how-to/operate-a-cluster/use-snapshots-for-offline-or-ci-diffs/)
for the same workflow run by hand from a workstation.
:::