diff --git a/.github/workflows/ns8-module.yml b/.github/workflows/ns8-module.yml index 76ef09f1..5e83009f 100644 --- a/.github/workflows/ns8-module.yml +++ b/.github/workflows/ns8-module.yml @@ -62,6 +62,26 @@ jobs: fi echo "$scripts" | awk '$1!="100755" {print "not executable in index:", $4; bad=1} END{exit bad}' + # Run the real push-manifest.sh end to end against a scratch storage and + # an oci: destination, so no registry and no credentials are needed. PR + # builds push nothing, which means the manifest step is skipped on PRs and + # a green run says nothing about it. Both earlier publish failures (a bad + # subcommand, then a bad argument count) lived in that step and only + # surfaced on a release push, so exercise it here instead. + # push-manifest.sh refuses to publish a list that is missing an arch, so a + # clean exit is the assertion. + - name: Selftest the multi-arch manifest assembly + run: | + set -euo pipefail + tmp=$(mktemp -d) + buildah from --name ocr-selftest-amd64 --arch amd64 scratch >/dev/null + buildah commit ocr-selftest-amd64 ocr-selftest/ns8-calnode:build-amd64 >/dev/null + buildah from --name ocr-selftest-arm64 --arch arm64 scratch >/dev/null + buildah commit ocr-selftest-arm64 ocr-selftest/ns8-calnode:build-arm64 >/dev/null + REPOBASE=ocr-selftest TARGETARCHES="amd64 arm64" IMAGETAGS=selftest \ + PUSH_DESTINATION="oci:${tmp}/out" bash packaging/ns8/push-manifest.sh + echo "manifest selftest OK" + build: needs: validate runs-on: ubuntu-latest diff --git a/packaging/ns8/push-manifest.sh b/packaging/ns8/push-manifest.sh index f879ca14..b8d3dfd7 100755 --- a/packaging/ns8/push-manifest.sh +++ b/packaging/ns8/push-manifest.sh @@ -18,6 +18,9 @@ # REPOBASE module image namespace, default ghcr.io/calnode # TARGETARCHES space-separated arches to assemble, required # IMAGETAGS space-separated tags to publish, default "latest" +# PUSH_DESTINATION push target override, default docker://. Set to an +# oci: path to exercise the whole script with no registry, +# which is what the CI selftest does. # # Example: # TARGETARCHES="amd64 arm64" IMAGETAGS="1.2.3 1.2 latest" bash push-manifest.sh @@ -48,15 +51,23 @@ done for imagetag in "${imagetags[@]}"; do manifest="${repobase}/${reponame}:${imagetag}" # Drop any stale manifest so a retry cannot inherit old entries. - buildah manifest rm --all "${manifest}" 2>/dev/null || true + buildah manifest rm "${manifest}" 2>/dev/null || true buildah manifest create "${manifest}" for arch in "${arches[@]}"; do - buildah manifest add \ - "${manifest}" \ - "${repobase}/${reponame}:build-${arch}" \ - "docker.io/calnode/ns8-calnode:${imagetag}-${arch}" + # Exactly two arguments: buildah 5.x takes only the list and the + # image, with no instance-name argument. + buildah manifest add "${manifest}" "${repobase}/${reponame}:build-${arch}" done - buildah manifest push --all "${manifest}" "docker://${manifest}" + # Refuse to publish a partial manifest. If any arch silently failed to + # join the list, an arm64 node would get an image it cannot run. + count=$(buildah manifest inspect "${manifest}" | grep -c '"architecture"') + if [ "$count" -ne "${#arches[@]}" ]; then + echo "Refusing to publish ${manifest}: it holds ${count} arches," \ + "expected ${#arches[*]}" >&2 + buildah manifest rm "${manifest}" 2>/dev/null || true + exit 1 + fi + buildah manifest push --all "${manifest}" "${PUSH_DESTINATION:-docker://${manifest}}" buildah manifest rm "${manifest}" echo "Published ${manifest} (${arches[*]})" done \ No newline at end of file