Files
dtourolle a3827646b9 feat(ci): CPU builder image for the unit-test workflow
TRACES: DP-007 | PR-004

Pinned by tag rather than :latest, so a workflow run is reproducible against
the image it was written for.
2026-08-04 14:53:12 +02:00

119 lines
4.5 KiB
Bash

#!/bin/bash
# build_builder_image.sh — build and publish the DP-007 CI builder image to the
# Gitea container registry.
#
# TRACES: DP-007 | PR-004
#
# Usage:
# scripts/ci/build_builder_image.sh # build only, tag v1
# scripts/ci/build_builder_image.sh --push # build and push
# scripts/ci/build_builder_image.sh --tag v2 --push # bump the pinned tag
# scripts/ci/build_builder_image.sh --no-cache # force a clean rebuild
#
# The tag is the contract with CI. .gitea/workflows/unit-tests.yml names an
# explicit tag in its `container:` block and never `latest`, so that rebuilding
# the image cannot silently change what a previous green build meant. Bumping
# the dependency set means bumping the tag AND editing the workflow — the two
# edits landing in the same commit is the point, not an inconvenience.
#
# Registry auth: this script does not log in. Do it once, out of band:
# docker login gitea.tourolle.paris
# The CI host is already authenticated this way (its cached credentials in
# ~/.docker/config.json are what the kpnpp-builder push relies on), so a
# workflow that calls this script needs no secret plumbing.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
REGISTRY="gitea.tourolle.paris"
OWNER="dtourolle"
IMAGE="sae-builder-cpu"
DOCKERFILE="Dockerfile.builder-cpu"
# The tag CI pins to today. Keep this in step with the `container.image` line in
# .gitea/workflows/unit-tests.yml; the workflow asserts at run time that the
# image it landed in reports this same version, so a drift shows up as a failed
# job rather than as a build against the wrong toolchain.
TAG="v1"
PUSH=0
EXTRA_ARGS=()
while [ $# -gt 0 ]; do
case "$1" in
--push) PUSH=1 ;;
--tag) TAG="${2:?--tag needs a value}"; shift ;;
--no-cache) EXTRA_ARGS+=(--no-cache) ;;
-h|--help) sed -n '2,30p' "${BASH_SOURCE[0]}"; exit 0 ;;
*) echo "error: unknown argument '$1'" >&2; exit 2 ;;
esac
shift
done
if [ "$TAG" = "latest" ]; then
echo "error: refusing to build the tag 'latest'." >&2
echo "DP-007 requires CI to pin an immutable tag. A moving 'latest' means a" >&2
echo "rebuild retroactively changes what every earlier green build proved." >&2
exit 2
fi
REF="${REGISTRY}/${OWNER}/${IMAGE}:${TAG}"
# A second tag carrying the commit that produced the image. The workflow pins
# the human-readable tag; this one is the audit trail — given any image you can
# recover the Dockerfile that built it.
SHA="$(git -C "$REPO_ROOT" rev-parse --short HEAD)"
REF_SHA="${REGISTRY}/${OWNER}/${IMAGE}:${TAG}-${SHA}"
# The Dockerfile COPYs nothing from the repository on purpose (see its closing
# comment), so the build context is an empty directory rather than the repo
# root. Sending ~1 GB of models, fixtures and experiment data to the daemon for
# a build that reads none of it is pure latency.
CONTEXT="$(mktemp -d)"
trap 'rm -rf "$CONTEXT"' EXIT
echo "=== building ${REF}"
echo " dockerfile: ${REPO_ROOT}/${DOCKERFILE}"
echo " context: (empty — the image embeds no repository content)"
echo
echo " Expect this to take a while: OpenCV 5 is compiled from source because"
echo " no Debian release ships it. That cost is paid once per image, which is"
echo " the entire reason DP-007 asks for a prebuilt image instead of"
echo " installing dependencies inside each CI run."
echo
docker build \
"${EXTRA_ARGS[@]}" \
--build-arg "IMAGE_TAG=${TAG}" \
-f "${REPO_ROOT}/${DOCKERFILE}" \
-t "${REF}" \
-t "${REF_SHA}" \
"${CONTEXT}"
echo
echo "=== built"
docker image inspect "${REF}" --format ' {{.RepoTags}} {{.Size}} bytes'
docker run --rm "${REF}" sh -c 'echo " SAE_BUILDER=$SAE_BUILDER version=$SAE_BUILDER_VERSION ort=$SAE_ORT_VERSION opencv=$SAE_OPENCV_VERSION"'
if [ "$PUSH" -eq 0 ]; then
echo
echo "Not pushed. Re-run with --push, or push by hand:"
echo " docker push ${REF}"
echo " docker push ${REF_SHA}"
exit 0
fi
echo
echo "=== pushing"
# No `latest` tag is pushed, by design. Publishing one invites a workflow to use
# it, and DP-007 exists to prevent exactly that.
docker push "${REF}"
docker push "${REF_SHA}"
echo
echo "=== published ${REF}"
echo "If this was a dependency-set change, bump the tag in"
echo " .gitea/workflows/unit-tests.yml (container.image)"
echo " scripts/ci/build_builder_image.sh (TAG, above)"
echo "in the same commit, so no run can build against an image the repository"
echo "does not describe."