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
|
## 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
|
- **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
|
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
|
on a **branch push** (`event: push` + `branch: develop/staging/production`) and
|
||||||
|
|||||||
+52
-3
@@ -679,11 +679,44 @@ let
|
|||||||
registryHost ? "git.oleks.space",
|
registryHost ? "git.oleks.space",
|
||||||
registryOwner ? "oleks",
|
registryOwner ? "oleks",
|
||||||
passEntry ? "infra/gitea/personal_access_token_packages_rw",
|
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
|
let
|
||||||
head = preamble { inherit registryHost registryOwner passEntry; };
|
head = preamble { inherit registryHost registryOwner passEntry; };
|
||||||
localArches = lib.attrNames images;
|
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).
|
# per-arch stage: realise the image's copy-to closure locally (no push).
|
||||||
mkArchStage =
|
mkArchStage =
|
||||||
arch:
|
arch:
|
||||||
@@ -696,7 +729,7 @@ let
|
|||||||
echo "→ staging ${imageName}:$VERSION-${arch} (build closure only, no push)"
|
echo "→ staging ${imageName}:$VERSION-${arch} (build closure only, no push)"
|
||||||
d="$(parity_stage_reset ${lib.escapeShellArg arch})"
|
d="$(parity_stage_reset ${lib.escapeShellArg arch})"
|
||||||
# Realise the nix2container copyTo wrapper (its closure is the image).
|
# 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 'oci %s %s %s\n' ${lib.escapeShellArg imageName} "$VERSION" "${arch}" >"$d/.parity-meta"
|
||||||
printf '%s\n' "$out" >"$d/copy-to-path"
|
printf '%s\n' "$out" >"$d/copy-to-path"
|
||||||
echo " staged copyTo: $out"
|
echo " staged copyTo: $out"
|
||||||
@@ -715,7 +748,8 @@ let
|
|||||||
digestArch = arch: ''
|
digestArch = arch: ''
|
||||||
echo "→ ${imageName}:$VERSION-${arch} (local build, no registry contact)"
|
echo "→ ${imageName}:$VERSION-${arch} (local build, no registry contact)"
|
||||||
ocidir="$(mktemp -d)"
|
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/$(
|
digest="$(skopeo manifest-digest "$ocidir/blobs/sha256/$(
|
||||||
jq -r '.manifests[0].digest | sub("sha256:";"")' "$ocidir/index.json"
|
jq -r '.manifests[0].digest | sub("sha256:";"")' "$ocidir/index.json"
|
||||||
)")"
|
)")"
|
||||||
@@ -758,7 +792,10 @@ let
|
|||||||
tok="$(parity_resolve_token)"
|
tok="$(parity_resolve_token)"
|
||||||
parity_registry_preflight
|
parity_registry_preflight
|
||||||
creds="$PARITY_REGISTRY_OWNER:$tok"
|
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" \
|
--dest-creds "$creds" \
|
||||||
"docker://${imageName}:$VERSION-${arch}"
|
"docker://${imageName}:$VERSION-${arch}"
|
||||||
echo "Pushed ${imageName}:$VERSION-${arch}"
|
echo "Pushed ${imageName}:$VERSION-${arch}"
|
||||||
@@ -809,6 +846,18 @@ let
|
|||||||
done
|
done
|
||||||
regctl index create "${imageName}:$VERSION" "''${refs[@]/#/--ref=}"
|
regctl index create "${imageName}:$VERSION" "''${refs[@]/#/--ref=}"
|
||||||
echo "Assembled index ${imageName}:$VERSION"
|
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