Repository navigation
feat: transactional air-gap bundle update with offline rollback - #611
Merged
shrinedogg merged 1 commit intoOct 18, 2026
Merged
Conversation
lmilbaum
force-pushed
the
feat/airgap-transactional-update
branch
from
October 16, 2026 18:49
926faa2 to
87e6308
Compare
lmilbaum
marked this pull request as ready for review
October 16, 2026 18:53
shrinedogg
approved these changes
Oct 17, 2026
shrinedogg
left a comment
Contributor
There was a problem hiding this comment.
APPROVE
Verified:
- CI all green, including "Build, lint, and test" (runs the new stub-mode test), "Assembler tests and strict site build", "Air-gap image digest pins", "Renovate airgap digest pinning", "Zarf mise asset pin".
- "Rendered Flux diff" skip confirmed legitimate: the PR is from a fork and
konflate.ymlskips fork PRs by design (job guardhead.repo.full_name == github.repository); the diff touches no cluster manifests (workflows, airgap scripts/tests, docs, AGENTS.md, mise.toml only). - Transactional apply traced in
airgap/scripts/update-package.sh: stage intoarchives/.staging/(lines 137-140) ->airgap_verify_packagesignature/checksum/SBOM decode via the sourcedverify-package.shlibrary (returns, never exits) -> hard-link backup of the active archive plus recorded sha256 (lines 151-154) -> atomicmvcutover (line 160) -> deploy -> on deploy failurerestore_previousverifies the backup digest, restores it, and exits 3 (lines 163-167, 106-119); on success state moves toawaiting-confirmand a secondapplyis refused untilconfirmorrollback. A package rejected in staging never touches the active archive (the test asserts content and inode are unchanged and that deploy is never invoked). - Offline rollback traced:
rollback()(lines 179-195) is purely local file operations (mkdir lock, state grep, sha256 check,mv,rm) with no network, docker, or registry access. The nightlyupdate-rollbackjob additionally runs the real-zarf rejection and restore scenarios undersudo unshare --net(air-gapped.yml), so the real-zarf path executes with the network namespace cut. - Ran
python3 airgap/tests/test-airgap-update-rollback.pylocally (stub mode, exactly as CI invokes it): 11/11 scenarios PASS (first install, first-install rollback, truncated package rejected, corrupted package rejected, deploy-failure restore reusing the prior inode, full apply/refuse/rollback/re-apply/confirm cycle, stale staging discarded, cutover-crash apply refused + rollback restores, cutover-crash confirm refused, tampered backup exits 4 keeping the active archive, naming invariants). - Probed paths the suite does not cover, locally with a stub deploy command and fake zarf: rollback with the
.bakdeleted entirely -> rc=4 fail-safe with the active archive untouched and state left for the operator; rollback/confirm with no state file -> rc=1 "no update in progress";statuson empty state -> clean "none" output; live lock (own pid) blocks apply with rc=1; stale lock with a dead pid is reclaimed automatically and the lock dir is removed after the run; unknown/corrupted phase value in the state file -> apply proceeds (harmless: the backup is always taken before the swap); usage errors rc=64; missing package file rc=1. - Mirrored the "Assembler tests and strict site build" job (docs.yml) locally under mise:
uv sync --lockedOK,uv run pytest -q-> 13 passed,uv run python tools/assemble_docs.py(30 files) +uv run mkdocs build --strict-> clean, so the docs/airgap.md rewrite renders under the strict build. - All 11 airgap tests from the validate job pass locally. Two local-only failures are environment, not code:
test-airgap-cert-manager-digest-values.pyfails on system Python 3.9.6 (PEP 604str | Noneat import) and passes on the pinned 3.15;test-airgap-failure-notification.pyneeds PyYAML on a bare 3.15 interpreter (present in CI). - Naming invariants hold:
ACTIVE_NAME==offline-run.shPACKAGE_INPUTdefault ==zarf.yamlmetadata.version 0.1.0, and no auxiliary name (.bak,.bak.sha256,.staging,.update-state,.update.lock) matches the*.tardocker-load glob instage-and-create-cluster.sh. git check-ignoreconfirmsairgap/archives/(.gitignore line 21) covers every state/backup file the script writes.- Docs consistent: the new "Updating a deployed bundle" section matches the code (exit codes 0/1/2/3/4/64, crash-recovery table, explicit scope: archive-only transactionality, cluster state is never rolled back, redeploy with
offline-run.sh); AGENTS.md is updated to match; no dangling references to the old "Update drill" section; the referenced tracking issue #369 ("airgap: transactional bundle update with tested offline rollback") is open. - Commit subject matches the PR title and the diff.
Note (non-blocking):
- The
update-rollbackjob triggers only on schedule/workflow_dispatch, so its real-zarf mode (--zarf-packageunderunshare --net) first executes on the next nightly after merge; PR CI covers the script logic in stub mode. apply()(lines 128-135) refuses onlycutover|awaiting-confirmphases; an unknown or corrupted phase value lets a new apply proceed. There is no data-loss path (the backup is always created before the swap), but an explicit allowlist of known phases would be tighter.- Line 140 uses a global
sync; syncing just the staging directory would be lighter. Cosmetic.
…rsquad#369) - Add verify-package.sh: shared library for signature/SBOM verification, sourced by offline-run.sh and update-package.sh - Add update-package.sh: apply/rollback/confirm/status subcommands; stages new package, verifies before cutover, keeps .bak until confirm; auto-restores prior archive on deploy failure (archive-level rollback only) - Add test-airgap-update-rollback.py: stub mode (validate + CI) and real-zarf mode (nightly update-rollback job under unshare --net) - Add update-rollback job to air-gapped.yml and report-status needs - Wire stub test into mise validate and validate.yml - Update verify-identity test, workflow-config test, failure-notification test, docs/airgap.md, and AGENTS.md Closes polarsquad#369 Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
shrinedogg
force-pushed
the
feat/airgap-transactional-update
branch
from
October 18, 2026 05:15
87e6308 to
0457ddb
Compare
shrinedogg
added a commit
to lmilbaum/krops
that referenced
this pull request
Oct 18, 2026
Restores polarsquad#569's 21-file set (worker lifecycle tag repair CronJob, bootstrap/pivot run-tag wiring, docs, and regression tests) on top of the scoped revert from polarsquad#570. The resulting tree is byte-identical to main at 6a5f273 (pre-revert), i.e. a pure round-trip: polarsquad#572's audit runbook, polarsquad#592's aws-cli pin, polarsquad#508's ambient-credentials rework, polarsquad#546's KCC chart consumption, and polarsquad#611's air-gap update feature are all preserved. Signed-off-by: ps-jshriner <[email protected]>
shrinedogg
added a commit
that referenced
this pull request
Oct 18, 2026
Restores #569's 21-file set (worker lifecycle tag repair CronJob, bootstrap/pivot run-tag wiring, docs, and regression tests) on top of the scoped revert from #570. The resulting tree is byte-identical to main at 6a5f273 (pre-revert), i.e. a pure round-trip: #572's audit runbook, #592's aws-cli pin, #508's ambient-credentials rework, #546's KCC chart consumption, and #611's air-gap update feature are all preserved. Signed-off-by: ps-jshriner <[email protected]> Co-authored-by: ps-jshriner <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
airgap/scripts/verify-package.sh(shared byoffline-run.shand the new update script).airgap/scripts/update-package.shwithapply,rollback,confirm, andstatussubcommands. Theapplyflow stages the new package, verifies it completely (signature, SBOM, digests) before touching the active archive, then cuts over atomically. The prior archive is kept as.bakuntilconfirm. On deploy failure, the prior archive is restored automatically. Only the package archive is transactional; live cluster state is not rolled back.airgap/tests/test-airgap-update-rollback.pywith a stub mode (runs inmise run validateand CI without a real package) and a--zarf-packagereal mode (runs in the new nightlyupdate-rollbackjob undersudo unshare --net).update-rollbackjob to.github/workflows/air-gapped.ymland toreport-statusneeds so nightly failures open the tracking issue.airgap/archives/is the artifact boundary; ephemeral CI artifacts; operators transfer the archive manually.How to test
python3 airgap/tests/test-airgap-update-rollback.py: stub mode, no zarf needed, runs inmise run validate.mise run validate: includes the above.air-gappedworkflow on this branch (gh workflow run air-gapped.yml --ref feat/airgap-transactional-update); check thatupdate-rollbackis green andisolated-deployis unaffected.ZARF_VERIFY_KEY=/path/cosign.pub python3 airgap/tests/test-airgap-update-rollback.py --zarf-package airgap/archives/zarf-package-krops-airgap-arm64-0.1.0.tar.zstunder Wi-Fi off orsudo unshare --net.Reviewer summary
🤖 Generated with Claude Code