Mirror images #36
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: "Mirror images" | |
on: | |
schedule: | |
- cron: '0 0 * * *' | |
workflow_dispatch: | |
env: | |
IMAGES: | | |
node | |
alpine | |
BATCH_SIZE: 500 | |
concurrency: | |
group: "mirror-images-${{ github.workflow }}" | |
jobs: | |
simulate-activity: | |
name: "Simulate repo activity" | |
runs-on: ubuntu-latest | |
permissions: | |
contents: write | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Simulate repository activity to keep schedule active | |
run: | | |
git config user.name "github-actions[bot]" | |
git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
git pull origin simulate-activity || true | |
git checkout simulate-activity | |
git commit --amend --no-edit | |
git push origin simulate-activity --force || true | |
prepare: | |
name: "Prepare matrix" | |
runs-on: ubuntu-latest | |
outputs: | |
matrix_json: ${{ steps.prepare-matrix.outputs.matrix_json }} | |
steps: | |
- name: Prepare matrix for mirror job | |
id: prepare-matrix | |
shell: bash | |
run: | | |
set -euo pipefail | |
python3 -u - <<EOF | |
import json | |
import subprocess | |
from pathlib import Path | |
batch_size = int("${{ env.BATCH_SIZE }}") | |
images = """${{ env.IMAGES }}""".splitlines() | |
outdir = Path("batches") | |
outdir.mkdir(exist_ok=True) | |
matrix = [] | |
for image in images: | |
src_image_name = image | |
dst_image_name = src_image_name.replace("/", "-").replace(".", "") + "-mirror" | |
if "/" not in src_image_name: | |
src_image_name = f"library/{src_image_name}" | |
src_image = f"docker.io/{src_image_name}" | |
dst_image = f"ghcr.io/${{ github.repository_owner }}/{dst_image_name}" | |
info_json = subprocess.check_output(["skopeo", "list-tags", f"docker://{src_image}"], text=True) | |
info = json.loads(info_json) | |
batch_num = 1 | |
for i in range(0, len(info["Tags"]), batch_size): | |
batch = info["Tags"][i:i + batch_size] | |
payload = { | |
"src_image": src_image, | |
"dst_image": dst_image, | |
"tags": batch, | |
} | |
with open(outdir / f"{image}-{batch_num}.json", "w") as f: | |
json.dump(payload, f, indent=2) | |
matrix.append({ | |
"image": image, | |
"batch_num": batch_num, | |
}) | |
batch_num += 1 | |
print("::group::Matrix") | |
print(json.dumps(matrix, indent=2)) | |
print("::endgroup:") | |
with open("$GITHUB_OUTPUT", "a") as f: | |
f.write(f"matrix_json={json.dumps(matrix)}\n") | |
EOF | |
- name: Upload batches artifacts | |
uses: actions/upload-artifact@v4 | |
with: | |
name: batches | |
path: batches | |
retention-days: 1 | |
mirror: | |
name: "Mirror ${{ matrix.batch.image }} (batch ${{ matrix.batch.batch_num }})" | |
runs-on: ubuntu-latest | |
needs: prepare | |
permissions: | |
packages: write | |
strategy: | |
matrix: | |
batch: ${{ fromJSON(needs.prepare.outputs.matrix_json) }} | |
fail-fast: false | |
steps: | |
- name: Download batches artifact | |
uses: actions/download-artifact@v4 | |
with: | |
name: batches | |
- name: Log into GHCR with Skopeo | |
run: | | |
echo "${{ secrets.GITHUB_TOKEN }}" | \ | |
skopeo login ghcr.io -u ${{ github.actor }} --password-stdin | |
- name: Mirror image | |
run: | | |
set -euo pipefail | |
batch_file="${{ matrix.batch.image }}-${{ matrix.batch.batch_num }}.json" | |
echo "::group::Batch file: $batch_file" | |
cat "$batch_file" | |
echo | |
echo "::endgroup::" | |
src_image=$(jq -r '.src_image' "$batch_file") | |
dst_image=$(jq -r '.dst_image' "$batch_file") | |
mapfile -t tags < <(jq -r '.tags[]' "$batch_file") | |
echo "Will copy ${{ matrix.batch.image }} ($src_image → $dst_image), batch ${{ matrix.batch.batch_num }}" | |
for tag in "${tags[@]}"; do | |
echo "::group::Copying $src_image:$tag → $dst_image:$tag" | |
status=0 | |
skopeo copy --all -f oci -f v2s2 "docker://$src_image:$tag" "docker://$dst_image:$tag" || status=$? | |
echo "::endgroup::" | |
if [[ $status -ne 0 ]]; then | |
echo "::error::Failed to copy $src_image:$tag → $dst_image:$tag (status: $status)" | |
fi | |
done |