ci: call the shared build-image script instead of an inline copy (infra#309)
Wave 2. The buildctl/skopeo step here was one of nineteen near-identical copies; the implementation now lives in .gitea/build-image.sh, byte-identical to mathias/infra's ci-templates/build-image.sh and held there by a drift gate. Labels, tags and build-args are inputs. Behaviour unchanged: same registry, same :sha/:latest/version tags, same revision and source labels. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011PL5XcG68SvQqz3vGVbWbf
This commit is contained in:
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/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`
|
||||
# TARGET optional, multi-stage build target (--opt target=)
|
||||
# BUILD_ARGS optional, space-separated K=V pairs
|
||||
# BUILD_SECRETS optional, space-separated buildkit secret specs, each
|
||||
# `id=NAME,src=PATH`. Pass the SPEC only -- the caller
|
||||
# writes the secret to PATH itself. A secret VALUE must
|
||||
# never be passed here: it would land in this script's
|
||||
# argv and therefore in the CI log.
|
||||
# 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 "${TARGET:-}" ]; then
|
||||
args+=(--opt "target=${TARGET}")
|
||||
fi
|
||||
for spec in ${BUILD_SECRETS:-}; do
|
||||
# Opaque id=NAME,src=PATH -- buildkit reads the file, nothing is echoed.
|
||||
args+=(--secret "${spec}")
|
||||
done
|
||||
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"
|
||||
+18
-44
@@ -58,53 +58,27 @@ jobs:
|
||||
REF="${{ github.ref }}"
|
||||
if [[ "$REF" == refs/tags/v* ]]; then
|
||||
echo "version-tag=${REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# The ldflags version stamp: git tag on a v* build, else short sha.
|
||||
# Computed here in shell, not as a `${{ a || b }}` expression, so it
|
||||
# cannot depend on how the runner treats an empty step output -- a
|
||||
# wrong VERSION would be baked into the binary silently.
|
||||
echo "version=${SHA}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Build and push to local registry
|
||||
run: |
|
||||
set -e
|
||||
# infra#241: buildctl, not buildah. Rootless buildah in gitea-runner's
|
||||
# hardened securityContext (capabilities drop ALL,
|
||||
# allowPrivilegeEscalation false) cannot remap the UID/GID range
|
||||
# newuidmap/newgidmap need on a fresh base-image pull, and the pod has
|
||||
# no OCI runtime at all -- so this Dockerfile's `RUN apk add` / `RUN go
|
||||
# build` could not execute there either. buildctl is a thin client
|
||||
# that 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 stops being in the picture. Same pattern as
|
||||
# mathias/infra's segment-embedder.yml.
|
||||
BUILDKIT_HOST="unix:///run/buildkit/buildkitd.sock"
|
||||
REGISTRY="localhost:5000"
|
||||
REF="${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.sha-tag }}"
|
||||
# Stamp the real build version: the git tag on a v* build, else the short sha.
|
||||
VERSION="${{ steps.meta.outputs.version-tag }}"
|
||||
# `if`, not `[ -z ] &&`: under `set -e` the && form exits the step
|
||||
# whenever VERSION is already set.
|
||||
if [ -z "$VERSION" ]; then VERSION="${{ steps.meta.outputs.sha-tag }}"; fi
|
||||
TAR_FILE="$(mktemp)"
|
||||
trap 'rm -f "$TAR_FILE"' EXIT
|
||||
|
||||
buildctl --addr "$BUILDKIT_HOST" build \
|
||||
--frontend dockerfile.v0 \
|
||||
--local context=. \
|
||||
--local dockerfile=. \
|
||||
--opt build-arg:VERSION="${VERSION}" \
|
||||
--opt label:org.opencontainers.image.revision="${{ github.sha }}" \
|
||||
--opt label:org.opencontainers.image.source="${{ github.repositoryUrl }}" \
|
||||
--output type=oci,dest="$TAR_FILE"
|
||||
|
||||
# buildctl has no registry-push equivalent to `buildah push`; skopeo
|
||||
# copies the OCI archive up, once per tag.
|
||||
skopeo copy --dest-tls-verify=false \
|
||||
oci-archive:"$TAR_FILE" docker://${REF}
|
||||
skopeo copy --dest-tls-verify=false \
|
||||
oci-archive:"$TAR_FILE" docker://${REGISTRY}/${{ env.IMAGE }}:latest
|
||||
if [ -n "${{ steps.meta.outputs.version-tag }}" ]; then
|
||||
skopeo copy --dest-tls-verify=false \
|
||||
oci-archive:"$TAR_FILE" \
|
||||
docker://${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.version-tag }}
|
||||
fi
|
||||
echo "✓ Image pushed to ${REF}"
|
||||
# infra#309: implementation lives in .gitea/build-image.sh, a
|
||||
# byte-identical copy of mathias/infra's ci-templates/build-image.sh.
|
||||
# Do not edit it here -- edit the canonical file, run `task ci:sync`,
|
||||
# and infra's drift gate names any repo still carrying an old copy.
|
||||
env:
|
||||
SHA_TAG: ${{ steps.meta.outputs.sha-tag }}
|
||||
VERSION_TAG: ${{ steps.meta.outputs.version-tag }}
|
||||
GIT_SHA: ${{ github.sha }}
|
||||
IMAGE_SOURCE: ${{ github.repositoryUrl }}
|
||||
BUILD_ARGS: VERSION=${{ steps.meta.outputs.version }}
|
||||
run: bash .gitea/build-image.sh
|
||||
|
||||
# No smoke test here. The old one ran `sudo k3s ctr run` against the host's
|
||||
# containerd: written for the Arch host runner, impossible in the
|
||||
|
||||
Reference in New Issue
Block a user