From cfd55aeedb863a5f69e84a9c81253a3e7bdc5c58 Mon Sep 17 00:00:00 2001 From: mathias Date: Tue, 25 Aug 2026 08:51:53 +0000 Subject: [PATCH] ci: carry the shared build-image script (infra#309) Byte-identical copy of mathias/infra's ci-templates/build-image.sh. A template that carries its own copy of the build LOGIC manufactures a new copy of every bug in it -- which is exactly how rootless buildah reached a dozen repos (infra#241). Now it manufactures a copy of one file that a drift gate keeps current instead. --- .gitea/build-image.sh | 108 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 .gitea/build-image.sh diff --git a/.gitea/build-image.sh b/.gitea/build-image.sh new file mode 100644 index 0000000..61a5d7a --- /dev/null +++ b/.gitea/build-image.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# Build an OCI image with buildctl and push it to the local registry. +# +# CANONICAL COPY: mathias/infra ci-templates/build-image.sh (infra#309). +# Every repo carries a byte-identical copy at .gitea/build-image.sh, written by +# `task ci:sync` and held in place by the drift gate. DO NOT EDIT THE COPIES -- +# edit here, re-sync, and the gate will tell you which repos still disagree. +# +# Why this exists: this step was hand-copied into a dozen repos, so migrating it +# off rootless buildah (infra#241) meant eleven near-identical commits, eleven +# verifications, and three separate rediscoveries of the same fix in the two +# repo templates and the gitea-ci skill. The differences between the copies were +# incidental -- a label here, a build-arg there -- never intentional. Those are +# inputs now, not forks of the implementation. +# +# Why buildctl and not buildah: the gitea-runner pod runs with +# `capabilities: drop [ALL]` and `allowPrivilegeEscalation: false`, which +# unconditionally blocks the setuid-root exec that rootless buildah's uid/gid +# remapping needs, and the pod has no OCI runtime at all. buildctl is a thin +# client -- it hands the build to the host's buildkitd over a socket, so the +# privileged layer work happens on the host and this pod's securityContext is +# not in the picture. buildah is absent from the runner image as of baked-v14 +# and its absence is asserted at pod start (infra#308). +# +# Inputs, all via environment: +# +# IMAGE (required) image name, e.g. cobalt-dingo +# REGISTRY default localhost:5000 +# SHA_TAG default `git rev-parse --short HEAD` +# VERSION_TAG optional extra tag, e.g. v1.2.3 on a tag build +# PUSH_LATEST default 1; set 0 to skip the :latest tag +# CONTEXT default . -- build context directory +# DOCKERFILE_DIR default $CONTEXT -- directory holding the Dockerfile +# DOCKERFILE_NAME optional, when the file is not named `Dockerfile` +# BUILD_ARGS optional, space-separated K=V pairs +# EXTRA_LABELS optional, space-separated K=V pairs +# GIT_SHA optional, stamped as org.opencontainers.image.revision +# IMAGE_SOURCE optional, stamped as org.opencontainers.image.source +# +# Note on shell style below: every conditional is a full `if` block, never +# `[ -n "$X" ] && cmd`. Under `set -e` the && form exits the script whenever the +# test is false, which is a real bug this estate has already shipped once. + +set -euo pipefail + +if [ -z "${IMAGE:-}" ]; then + echo "build-image: IMAGE is required" >&2 + exit 2 +fi + +REGISTRY="${REGISTRY:-localhost:5000}" +BUILDKIT_HOST="${BUILDKIT_HOST:-unix:///run/buildkit/buildkitd.sock}" +CONTEXT="${CONTEXT:-.}" +DOCKERFILE_DIR="${DOCKERFILE_DIR:-$CONTEXT}" +PUSH_LATEST="${PUSH_LATEST:-1}" +VERSION_TAG="${VERSION_TAG:-}" + +if [ -z "${SHA_TAG:-}" ]; then + SHA_TAG="$(git rev-parse --short HEAD)" +fi + +REF="${REGISTRY}/${IMAGE}:${SHA_TAG}" + +TAR_FILE="$(mktemp)" +trap 'rm -f "$TAR_FILE"' EXIT + +args=( + --frontend dockerfile.v0 + --local "context=${CONTEXT}" + --local "dockerfile=${DOCKERFILE_DIR}" +) + +if [ -n "${DOCKERFILE_NAME:-}" ]; then + args+=(--opt "filename=${DOCKERFILE_NAME}") +fi +if [ -n "${GIT_SHA:-}" ]; then + args+=(--opt "label:org.opencontainers.image.revision=${GIT_SHA}") +fi +if [ -n "${IMAGE_SOURCE:-}" ]; then + args+=(--opt "label:org.opencontainers.image.source=${IMAGE_SOURCE}") +fi +for kv in ${BUILD_ARGS:-}; do + args+=(--opt "build-arg:${kv}") +done +for kv in ${EXTRA_LABELS:-}; do + args+=(--opt "label:${kv}") +done + +echo "build-image: building ${REF}" +buildctl --addr "$BUILDKIT_HOST" build "${args[@]}" --output "type=oci,dest=${TAR_FILE}" + +# buildctl has no registry-push equivalent to `buildah push`, so skopeo copies +# the OCI archive up -- once per tag, from the same archive, so every tag points +# at bit-identical content. +push_tag() { + echo "build-image: pushing ${1}" + skopeo copy --dest-tls-verify=false "oci-archive:${TAR_FILE}" "docker://${1}" +} + +push_tag "$REF" +if [ "$PUSH_LATEST" = "1" ]; then + push_tag "${REGISTRY}/${IMAGE}:latest" +fi +if [ -n "$VERSION_TAG" ]; then + push_tag "${REGISTRY}/${IMAGE}:${VERSION_TAG}" +fi + +echo "✓ build-image: ${REF} pushed"