mirror-base-image.yaml pulled the upstream index digest on an amd64 runner and re-pushed it, publishing only the amd64 manifest; the python/golang mirrors had no arm64 variant. Replace pull/tag/push with a registry-to-registry `imagetools create` of the digest-pinned index via .gitea/scripts/ mirror-index-copy.sh: copy to <tag>-staging, assert linux/amd64+linux/arm64 are listed and the index digest equals the Docker Hub digest, then move the real tag and re-assert. A failed assertion never touches the real tag. Adds a local-registry test (.gitea/scripts/test-mirror-index-copy.sh). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0185ukXjXyosSkunDsdWz3mb
93 lines
4.5 KiB
YAML
93 lines
4.5 KiB
YAML
name: Mirror base images
|
|
run-name: weekly base-image mirror
|
|
|
|
# Copies each declared base image (the whole multi-arch INDEX, not one
|
|
# platform) from upstream to the in-house registry, so any of our images that FROM these don't depend on docker.io's
|
|
# Cloudflare R2 blob storage being reachable. The 2026-05-12 Cloudflare
|
|
# incident motivated this for python:3.12-slim and again for golang:1.25
|
|
# when the coraza-spoa build hit the same blob-fetch failure.
|
|
#
|
|
# Adding a new mirror = add one entry to the matrix below. The destination
|
|
# tag is always cloud-hosting-platform/<image>:<tag>, matching upstream.
|
|
|
|
on:
|
|
schedule:
|
|
# Mondays 06:00 UTC — outside customer peak hours and well before the
|
|
# typical Tuesday/Thursday push cycles. workflow_dispatch lets us trigger
|
|
# manually from the Gitea UI when upstream publishes patches.
|
|
- cron: '0 6 * * 1'
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
Mirror-Base:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
# fail-fast=false so one image's upstream being down doesn't block the
|
|
# others from refreshing.
|
|
fail-fast: false
|
|
matrix:
|
|
# hub: the Docker Hub IMAGE:TAG whose digest is resolved DIRECTLY at
|
|
# registry-1.docker.io (resolve-base-image.sh expects no docker.io/
|
|
# prefix; official images resolve as library/<image>).
|
|
image:
|
|
- { hub: 'python:3.12-slim', dst_path: 'cloud-hosting-platform/python', tag: '3.12-slim' }
|
|
- { hub: 'golang:1.25', dst_path: 'cloud-hosting-platform/golang', tag: '1.25' }
|
|
|
|
steps:
|
|
# Only for .gitea/scripts/resolve-base-image.sh.
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
# Supply chain: ask Docker Hub itself (over HTTPS, never via a mirror) which
|
|
# digest the tag points at right now, then pull exactly that digest. What
|
|
# lands in the in-house registry is therefore content-addressed to the
|
|
# digest Docker Hub reported - not whatever a `docker pull TAG` on the
|
|
# runner happened to resolve through any configured registry mirror.
|
|
# If the lookup fails the job FAILS and last week's mirror stays in place.
|
|
- name: Resolve ${{ matrix.image.hub }} digest (Docker Hub, direct)
|
|
id: base
|
|
env:
|
|
HUB_TAG: ${{ matrix.image.hub }}
|
|
run: |
|
|
set -euo pipefail
|
|
ref=$(.gitea/scripts/resolve-base-image.sh "$HUB_TAG")
|
|
[[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]] || { echo "::error::bad digest reference for ${HUB_TAG}"; exit 1; }
|
|
echo "base-image: ${HUB_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
|
echo "ref=${ref}" >> "$GITHUB_OUTPUT"
|
|
|
|
# imagetools needs buildx; same setup as the build workflows.
|
|
- name: Set up Docker Buildx
|
|
uses: https://github.com/docker/setup-buildx-action@v3
|
|
|
|
# The registry token is written ONLY by this trusted x86 runner step
|
|
# (unchanged from before; no new secret exposure).
|
|
- name: Login to in-house registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: repo.anhonesthost.net
|
|
username: ${{ secrets.CI_USER }}
|
|
password: ${{ secrets.CI_TOKEN }}
|
|
|
|
# Registry-to-registry copy of the WHOLE upstream index (all platforms,
|
|
# incl. linux/arm64). The old pull/tag/push re-published only the runner's
|
|
# amd64 manifest. mirror-index-copy.sh stages to <tag>-staging, asserts
|
|
# amd64+arm64 present and index digest == the Docker-Hub-reported digest,
|
|
# and only then moves the real tag, so a bad copy never replaces a good
|
|
# multi-arch mirror. Logic + tests: .gitea/scripts/mirror-index-copy.sh,
|
|
# .gitea/scripts/test-mirror-index-copy.sh.
|
|
- name: Copy index by digest, assert platforms + digest ${{ matrix.image.hub }}
|
|
# Values reach the script via env, never via ${{ }} inside run:.
|
|
env:
|
|
REF: ${{ steps.base.outputs.ref }}
|
|
DST_PATH: ${{ matrix.image.dst_path }}
|
|
DST_TAG: ${{ matrix.image.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
[[ "$REF" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]] || { echo "::error::bad digest reference '${REF}'"; exit 1; }
|
|
[[ "$DST_PATH" =~ ^[a-z0-9][a-z0-9._/-]*$ && "$DST_TAG" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]] || { echo "::error::bad destination"; exit 1; }
|
|
name="${REF%@*}"; digest="${REF#*@}"
|
|
[[ "$name" == */* ]] || name="library/${name}" # official images
|
|
.gitea/scripts/mirror-index-copy.sh \
|
|
"docker.io/${name}@${digest}" \
|
|
"repo.anhonesthost.net/${DST_PATH}:${DST_TAG}"
|