Skip to content

Mirror images

Mirror images #41

Workflow file for this run

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