Something went wrong. Try again.
A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
Something went wrong. Try again.
5.7 kB · 187 lines
Shell
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188#!/bin/bashset -e
# Usage functionusage() { echo "Usage: $0 <source-image> [target-image]" echo "" echo "Examples:" echo " $0 ghcr.io/evanjarrett/myapp:latest" echo " $0 ghcr.io/evanjarrett/myapp:latest atcr.io/evan.jarrett.net/myapp:latest" echo "" echo "If target-image is not specified, it will use atcr.io/<username>/<repo>:<tag>" exit 1}
# Check argumentsif [ $# -lt 1 ]; then usagefi
SOURCE_IMAGE="$1"TARGET_IMAGE="${2:-}"
# Parse source image to extract components# Format: [registry/]repository[:tag|@digest]parse_image_ref() { local ref="$1" local registry="" local repository="" local tag="latest"
# Remove digest if present (we'll fetch the manifest-list) ref="${ref%@*}"
# Extract tag if [[ "$ref" == *:* ]]; then tag="${ref##*:}" ref="${ref%:*}" fi
# Extract registry and repository if [[ "$ref" == */*/* ]]; then # Has registry registry="${ref%%/*}" repository="${ref#*/}" else # No registry, assume Docker Hub registry="docker.io" repository="$ref" fi
echo "$registry" "$repository" "$tag"}
# Parse source imageread -r SOURCE_REGISTRY SOURCE_REPO SOURCE_TAG <<< "$(parse_image_ref "$SOURCE_IMAGE")"
# If no target specified, auto-generate itif [ -z "$TARGET_IMAGE" ]; then # Extract just the repo name (last component) REPO_NAME="${SOURCE_REPO##*/}" # Try to extract username from source if [[ "$SOURCE_REPO" == */* ]]; then USERNAME="${SOURCE_REPO%/*}" USERNAME="${USERNAME##*/}" else USERNAME="default" fi TARGET_IMAGE="atcr.io/${USERNAME}/${REPO_NAME}:${SOURCE_TAG}"fi
# Parse target imageread -r TARGET_REGISTRY TARGET_REPO TARGET_TAG <<< "$(parse_image_ref "$TARGET_IMAGE")"
echo "=== Migrating multi-arch image ==="echo "Source: ${SOURCE_REGISTRY}/${SOURCE_REPO}:${SOURCE_TAG}"echo "Target: ${TARGET_REGISTRY}/${TARGET_REPO}:${TARGET_TAG}"echo ""
# Full source referenceSOURCE_REF="${SOURCE_REGISTRY}/${SOURCE_REPO}:${SOURCE_TAG}"TARGET_REF="${TARGET_REGISTRY}/${TARGET_REPO}:${TARGET_TAG}"
# Fetch the manifest listecho ">>> Fetching manifest list from source..."MANIFEST_JSON=$(docker manifest inspect "$SOURCE_REF" 2>/dev/null || { echo "Error: Failed to fetch manifest list. This may not be a multi-arch image." echo "Trying as single-arch image..."
# Try pulling as single image docker pull "$SOURCE_REF" docker tag "$SOURCE_REF" "$TARGET_REF" docker push "$TARGET_REF" echo "=== Migration complete (single-arch) ===" exit 0})
# Check if this is a manifest listMEDIA_TYPE=$(echo "$MANIFEST_JSON" | jq -r '.mediaType // .schemaVersion')if [[ ! "$MEDIA_TYPE" =~ "manifest.list" ]] && [[ ! "$MEDIA_TYPE" =~ "index" ]]; then echo "Warning: Source appears to be a single-arch image, not a manifest list." docker pull "$SOURCE_REF" docker tag "$SOURCE_REF" "$TARGET_REF" docker push "$TARGET_REF" echo "=== Migration complete (single-arch) ===" exit 0fi
echo "Found multi-arch manifest list"echo ""
# Extract platform information and digests (skip unknown/unknown for cosign artifacts)PLATFORMS=$(echo "$MANIFEST_JSON" | jq -r '.manifests[] | select(.platform.os != "unknown" and .platform.architecture != "unknown") | "\(.platform.os)|\(.platform.architecture)|\(.platform.variant // "")|\(.digest)"')
# Arrays to store pushed images for manifest creationdeclare -a PUSHED_IMAGESdeclare -a PLATFORM_INFO
# Process each platformwhile IFS='|' read -r os arch variant digest; do # Create platform tag (e.g., "linux-amd64" or "linux-arm-v7") PLATFORM_TAG="${os}-${arch}" if [ -n "$variant" ]; then PLATFORM_TAG="${PLATFORM_TAG}-${variant}" fi
echo ">>> Processing ${os}/${arch}${variant:+/$variant}..." echo " Digest: $digest"
# Pull by digest echo " Pulling image..." docker pull "${SOURCE_REGISTRY}/${SOURCE_REPO}@${digest}"
# Tag for target TARGET_PLATFORM_REF="${TARGET_REGISTRY}/${TARGET_REPO}:${TARGET_TAG}-${PLATFORM_TAG}" echo " Tagging as: ${TARGET_PLATFORM_REF}" docker tag "${SOURCE_REGISTRY}/${SOURCE_REPO}@${digest}" "${TARGET_PLATFORM_REF}"
# Push platform-specific image echo " Pushing..." docker push "${TARGET_PLATFORM_REF}"
# Store for manifest creation PUSHED_IMAGES+=("${TARGET_PLATFORM_REF}") PLATFORM_INFO+=("${os}|${arch}|${variant}")
echo ""done <<< "$PLATFORMS"
# Create multi-arch manifestecho ">>> Creating multi-arch manifest..."MANIFEST_CREATE_CMD="docker manifest create ${TARGET_REF}"for img in "${PUSHED_IMAGES[@]}"; do MANIFEST_CREATE_CMD="${MANIFEST_CREATE_CMD} --amend ${img}"done
eval "$MANIFEST_CREATE_CMD"echo ""
# Annotate each platformecho ">>> Annotating manifest with platform information..."for i in "${!PUSHED_IMAGES[@]}"; do IFS='|' read -r os arch variant <<< "${PLATFORM_INFO[$i]}"
ANNOTATE_CMD="docker manifest annotate ${TARGET_REF} ${PUSHED_IMAGES[$i]} --os ${os} --arch ${arch}" if [ -n "$variant" ]; then ANNOTATE_CMD="${ANNOTATE_CMD} --variant ${variant}" fi
echo " Annotating ${os}/${arch}${variant:+/$variant}..." eval "$ANNOTATE_CMD"doneecho ""
# Push the manifest listecho ">>> Pushing multi-arch manifest..."docker manifest push "${TARGET_REF}"echo ""
echo "=== Migration complete! ==="echo "You can now pull: docker pull ${TARGET_REF}"echo ""echo "Migrated platforms:"for i in "${!PLATFORM_INFO[@]}"; do IFS='|' read -r os arch variant <<< "${PLATFORM_INFO[$i]}" echo " - ${os}/${arch}${variant:+/$variant}"done