mkNix2ContainerPublish: add impureBuild + indexMovesLatest modes (cluster #205)
impureBuild: build the consumer's flake attr at run time via nix build --impure --sandbox false, instead of embedding the image closure as an eval-time build dep — required for images that fetch private artifacts with a token at build time (ii-agent). indexMovesLatest: publish-index also moves :latest, for repos that publish each arch on a separate CI agent and converge in a final index-only step. Both opt-in, default-off; existing consumers unchanged. Verified by eval in both modes.
This commit is contained in:
@@ -7,6 +7,30 @@ semantic versioning; the version is a conceptual tag (no git tag is created).
|
||||
|
||||
## Unreleased
|
||||
|
||||
- **Feature: `mkNix2ContainerPublish` impure-build + index-moves-latest modes
|
||||
(cluster #205).** Two opt-in args, both default-off so every existing consumer
|
||||
is byte-unchanged:
|
||||
- `impureBuild ? false` — for images that are built **impurely** (need a
|
||||
registry token / network / `--option sandbox false` at build time, e.g.
|
||||
ii-agent fetching private wheels). The default contract references
|
||||
`images.<arch>.copyTo` as an **eval-time store path** — a build dep of the
|
||||
publish app — which is fatal for an impure image (it would build under the
|
||||
pure sandbox). In impure mode each `images.<arch>` instead carries `attr`
|
||||
(a flake attribute on the *consumer's own* flake), and `stage`/`verify-digest`
|
||||
build it at **run time** via `nix build --impure --option sandbox false
|
||||
".#<attr>.copyTo"`, then read the path back; `publish` reads the staged
|
||||
copy-to path (no rebuild). The impure closure never becomes a build dep of
|
||||
the app.
|
||||
- `indexMovesLatest ? false` — when true, `publish-index` ALSO moves `:latest`
|
||||
(a dev-guarded digest copy of `:VERSION`) after assembling the index. For
|
||||
repos that publish each arch on a **separate CI agent** and converge in a
|
||||
final index-only step (ii-agent's `.woodpecker/{amd64,arm64}.yaml` +
|
||||
`publish-index`), `:latest` must move there rather than in `publish`.
|
||||
|
||||
Verified by eval (both modes): impure `stage`/`verify-digest` emit `nix build
|
||||
--impure`, `publish` delegates to the staged path, `publish-index` moves
|
||||
`:latest`; pure mode emits the store path, no impure build, and `publish-index`
|
||||
does NOT touch `:latest`.
|
||||
- **Fix: `pipeline-doctor` models branch-deploy repos (#204).** The dev-tag-guard
|
||||
check only accepted a `refs/tags/v*` tag gate, so web-app/CMS repos that deploy
|
||||
on a **branch push** (`event: push` + `branch: develop/staging/production`) and
|
||||
|
||||
+52
-3
@@ -679,11 +679,44 @@ let
|
||||
registryHost ? "git.oleks.space",
|
||||
registryOwner ? "oleks",
|
||||
passEntry ? "infra/gitea/personal_access_token_packages_rw",
|
||||
# IMPURE-BUILD mode (cluster #205). Default: each `images.<arch>` carries a
|
||||
# `copyTo` derivation referenced as an EVAL-TIME store path (a build dep of
|
||||
# the app) — correct for a pure image. But some images are built IMPURELY
|
||||
# (need a registry token / network / sandbox off at build time, e.g.
|
||||
# ii-agent fetching private wheels), so the closure CANNOT be a build dep
|
||||
# of the app. When `impureBuild = true`, each `images.<arch>` instead
|
||||
# carries `attr` (a flake attribute on the CONSUMER's own flake, e.g.
|
||||
# "frontend-image"), and stage/publish/verify-digest build it at RUN time
|
||||
# via `nix build --impure --option sandbox false ".#<attr>.copyTo"`.
|
||||
impureBuild ? false,
|
||||
# When true, `publish-index` ALSO moves `:latest` (a dev-guarded digest
|
||||
# copy of `:VERSION`) after assembling the index. Default off: `:latest`
|
||||
# is owned by the `publish` app. Repos that publish each arch on a SEPARATE
|
||||
# CI agent and converge in a final index-only step (ii-agent) need the
|
||||
# latest-move to live in `publish-index`. cluster #205.
|
||||
indexMovesLatest ? false,
|
||||
}:
|
||||
let
|
||||
head = preamble { inherit registryHost registryOwner passEntry; };
|
||||
localArches = lib.attrNames images;
|
||||
|
||||
# Resolve one arch's nix2container copy-to wrapper into shell var $1.
|
||||
# Pure: an eval-time store path (build dep of the app). Impure (cluster
|
||||
# #205): build the consumer's own flake attr at RUN time and read the path
|
||||
# back — the impure layers (token/network/sandbox-off) never become a build
|
||||
# dep of the app, which would otherwise fail under the pure sandbox.
|
||||
resolveCopyTo =
|
||||
arch: var:
|
||||
if impureBuild then
|
||||
''
|
||||
IMAGE_TAG="$VERSION-${arch}" nix build --impure --no-link \
|
||||
--option sandbox false ".#${images.${arch}.attr}.copyTo" >/dev/null
|
||||
${var}="$(IMAGE_TAG="$VERSION-${arch}" nix eval --impure --raw \
|
||||
".#${images.${arch}.attr}.copyTo")"
|
||||
''
|
||||
else
|
||||
"${var}=${lib.escapeShellArg "${images.${arch}.copyTo}"}";
|
||||
|
||||
# per-arch stage: realise the image's copy-to closure locally (no push).
|
||||
mkArchStage =
|
||||
arch:
|
||||
@@ -696,7 +729,7 @@ let
|
||||
echo "→ staging ${imageName}:$VERSION-${arch} (build closure only, no push)"
|
||||
d="$(parity_stage_reset ${lib.escapeShellArg arch})"
|
||||
# Realise the nix2container copyTo wrapper (its closure is the image).
|
||||
out=${lib.escapeShellArg "${images.${arch}.copyTo}"}
|
||||
${resolveCopyTo arch "out"}
|
||||
printf 'oci %s %s %s\n' ${lib.escapeShellArg imageName} "$VERSION" "${arch}" >"$d/.parity-meta"
|
||||
printf '%s\n' "$out" >"$d/copy-to-path"
|
||||
echo " staged copyTo: $out"
|
||||
@@ -715,7 +748,8 @@ let
|
||||
digestArch = arch: ''
|
||||
echo "→ ${imageName}:$VERSION-${arch} (local build, no registry contact)"
|
||||
ocidir="$(mktemp -d)"
|
||||
${lib.escapeShellArg "${images.${arch}.copyTo}"}/bin/copy-to "oci:$ocidir:${arch}" >/dev/null
|
||||
${resolveCopyTo arch "copyto"}
|
||||
"$copyto/bin/copy-to" "oci:$ocidir:${arch}" >/dev/null
|
||||
digest="$(skopeo manifest-digest "$ocidir/blobs/sha256/$(
|
||||
jq -r '.manifests[0].digest | sub("sha256:";"")' "$ocidir/index.json"
|
||||
)")"
|
||||
@@ -758,7 +792,10 @@ let
|
||||
tok="$(parity_resolve_token)"
|
||||
parity_registry_preflight
|
||||
creds="$PARITY_REGISTRY_OWNER:$tok"
|
||||
${lib.escapeShellArg "${images.${arch}.copyTo}"}/bin/copy-to \
|
||||
# Read the copy-to path the stage step just wrote (works for both
|
||||
# pure and impure modes — stage already realised/built the closure).
|
||||
copyto="$(cat "$PARITY_STAGE_DIR/${arch}/copy-to-path")"
|
||||
"$copyto/bin/copy-to" \
|
||||
--dest-creds "$creds" \
|
||||
"docker://${imageName}:$VERSION-${arch}"
|
||||
echo "Pushed ${imageName}:$VERSION-${arch}"
|
||||
@@ -809,6 +846,18 @@ let
|
||||
done
|
||||
regctl index create "${imageName}:$VERSION" "''${refs[@]/#/--ref=}"
|
||||
echo "Assembled index ${imageName}:$VERSION"
|
||||
${lib.optionalString indexMovesLatest ''
|
||||
# Repos that converge per-arch legs in this index-only step own the
|
||||
# ':latest' move here (rather than in `publish`). Dev-guarded; a pure
|
||||
# digest copy of the just-assembled ':VERSION', done LAST.
|
||||
case "$VERSION" in
|
||||
*dev*) echo " dev version $VERSION — refusing to move :latest" ;;
|
||||
*)
|
||||
regctl image copy "${imageName}:$VERSION" "${imageName}:latest"
|
||||
echo "→ ${imageName}:$VERSION -> :latest (digest copy)"
|
||||
;;
|
||||
esac
|
||||
''}
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user