From 36d003ab2ce49adc239e1382bed82556f65b6818 Mon Sep 17 00:00:00 2001 From: Xiaosong Pan Date: Wed, 30 Sep 2026 12:15:10 -0700 Subject: [PATCH] ci(eng-prod): mirror the sd shard redis images to ghcr [CLK-1591713] --- .github/workflows/mirror-images.yml | 63 +++++++++++++++++++++++++++++ CLAUDE.md | 5 ++- PUBLISH.md | 17 ++++++++ docker/mirror-images.txt | 9 +++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/mirror-images.yml create mode 100644 docker/mirror-images.txt diff --git a/.github/workflows/mirror-images.yml b/.github/workflows/mirror-images.yml new file mode 100644 index 0000000..63b2544 --- /dev/null +++ b/.github/workflows/mirror-images.yml @@ -0,0 +1,63 @@ +name: Mirror Images +on: + pull_request: + branches: + - main + paths: + - docker/mirror-images.txt + - .github/workflows/mirror-images.yml + push: + branches: + - main + paths: + - docker/mirror-images.txt + - .github/workflows/mirror-images.yml + workflow_dispatch: + +jobs: + # Copies every image in docker/mirror-images.txt from Docker Hub to + # ghcr.io//mirror/:, byte-for-byte (same digest, all + # platforms). Same model as push-images in ci.yml: the built-in GITHUB_TOKEN + # with packages: write, no registry PAT, and on pull_request events nothing is + # pushed, only checked. The pinned digest is what gets copied, so a tag that + # moves upstream only raises a warning. + mirror-images: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + packages: write + env: + MIRROR_PREFIX: ghcr.io/${{ github.repository_owner }}/mirror + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - name: Log in to GHCR + if: github.event_name != 'pull_request' + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | + skopeo login ghcr.io --username "${{ github.actor }}" --password-stdin + - name: Check or copy each image + run: | + set -euo pipefail + grep -Ev '^[[:space:]]*(#|$)' docker/mirror-images.txt | while read -r line; do + ref="${line%@*}" + digest="${line#*@}" + source="docker://docker.io/${ref%:*}@$digest" + skopeo inspect --raw "$source" > /dev/null + tag_digest="$(skopeo inspect --raw "docker://docker.io/$ref" | sha256sum | cut -d' ' -f1)" + if [[ "sha256:$tag_digest" != "$digest" ]]; then + echo "::warning::$ref now resolves to sha256:$tag_digest, not the pinned $digest; the pinned digest is still what gets mirrored." + fi + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "ok $ref@$digest" + continue + fi + dest="docker://$MIRROR_PREFIX/$ref" + skopeo copy --all --preserve-digests "$source" "$dest" + mirrored="sha256:$(skopeo inspect --raw "$dest" | sha256sum | cut -d' ' -f1)" + if [[ "$mirrored" != "$digest" ]]; then + echo "::error::$MIRROR_PREFIX/$ref has digest $mirrored after the copy, expected $digest." + exit 1 + fi + echo "mirrored $MIRROR_PREFIX/$ref@$digest" + done diff --git a/CLAUDE.md b/CLAUDE.md index d1c6f15..f6f92ae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,7 +31,10 @@ The primary downstream consumer of the images is a **separate repo, scripts (`root/entrypoint.NN-*.sh` run as root, `guest/entrypoint.NN-*.sh` as the runner user, in numeric order). - `docker/compose.yml` — local/integration testing of all three images together. -- `.github/workflows/ci.yml` — the only workflow. +- `.github/workflows/ci.yml` — tests and publishes the three images. +- `.github/workflows/mirror-images.yml` + `docker/mirror-images.txt` — copies + pinned third-party images (the `time-loop/sd` shard Redis) from Docker Hub to + `ghcr.io/time-loop/mirror/*`, so runners never pull Docker Hub anonymously. - `PUBLISH.md` — release + GHCR publishing instructions. ## CI / publishing diff --git a/PUBLISH.md b/PUBLISH.md index eeeb765..aa70986 100644 --- a/PUBLISH.md +++ b/PUBLISH.md @@ -35,6 +35,23 @@ Do this for `ci-storage`, `ci-scaler`, and `ci-runner`. This may require org admin privileges. Public visibility lets downstream consumers (e.g. `time-loop/sd`) pull the images anonymously. +## Mirrored third-party images + +`.github/workflows/mirror-images.yml` copies each image pinned in +`docker/mirror-images.txt` from Docker Hub to +`ghcr.io/time-loop/mirror/:` with the same digest, using the same +`GITHUB_TOKEN` login as above. It runs when either file changes on `main`, and +on demand via "Run workflow". To mirror a new image, add its +`:@` line and merge; the first run creates the package, +which then needs the same one-time switch to public: + +- `mirror/bitnamilegacy/redis-cluster` +- `mirror/bitnamilegacy/redis` +- `mirror/library/alpine` + +Add the image here before any consumer points at it: a consumer that pulls a +missing or still-private mirror package fails its pull. + ## Release a new GitHub Action version To release a new GitHub Action version to the GitHub Marketplace (example for v1 diff --git a/docker/mirror-images.txt b/docker/mirror-images.txt new file mode 100644 index 0000000..69c5ad0 --- /dev/null +++ b/docker/mirror-images.txt @@ -0,0 +1,9 @@ +# Third-party images mirrored to ghcr.io//mirror/: by +# .github/workflows/mirror-images.yml, so self-hosted runners pull them from +# GHCR instead of anonymously from Docker Hub. +# +# One image per line: :@. The digest pins what is +# copied; the mirror keeps the same digest, so consumers can pin it too. +bitnamilegacy/redis-cluster:6.2.16@sha256:d973a2aa8b6688190ca4e4544b2ff859ef1e9f8081518558270df34e23ff1df7 +bitnamilegacy/redis:6.2.16@sha256:116419dc09f37b2c37e47e1a2cef258620ca1a30bae379b3660cf78fc0e7fc99 +library/alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507