#!/usr/bin/env bash set -Eeuo pipefail # for real pushes, this would be "library" targetOrg='trollin' # https://github.com/tianon/dockerhub-public-proxy publicProxy="$DOCKERHUB_PUBLIC_PROXY" _curl() { curl -fsSL --retry 3 "$@" } split_img() { local img="$1"; shift local repoVar="$1"; shift local tagVar="$1"; shift local repo="${img%%:*}" local tag="${img#$repo:}" [ "$tag" != "$repo" ] || tag='latest' local printfEval printfEval="$(printf "declare -g $repoVar=%q $tagVar=%q" "$repo" "$tag")" eval "$printfEval" } arch_to_ns() { local arch="$1"; shift case "$arch" in windows-*) echo -n "win${arch#windows-}" ;; *) echo -n "$arch" ;; esac } fetch_digest() { local repo="$1"; shift local man="$1"; shift local curl curl="$(_curl --head "$publicProxy/v2/$repo/manifests/$man")" || return 1 curl="$(tr -d '\r' <<<"$curl")" || return 1 local digest digest="$(sed --quiet --regexp-extended -e '/^docker-content-digest:[[:space:]]+/ s/// p' <<<"${curl,,}")" || return 1 [ -n "$digest" ] || return 1 [[ "$digest" == sha256:* ]] || return 1 echo -n "$digest" } fetch_manifest() { local repo="$1"; shift local digest="$1"; shift [[ "$digest" == sha256:* ]] || return 1 _curl "$publicProxy/v2/$repo/manifests/$digest" || return 1 } fetch_blob() { local repo="$1"; shift local digest="$1"; shift [[ "$digest" == sha256:* ]] || return 1 _curl "$publicProxy/v2/$repo/blobs/$digest" || return 1 } arch_to_platform() { local arch="$1"; shift local goos="${arch%-*}" [ "$goos" != "$arch" ] || goos='linux' local goarch="${arch#$goos-}" variant= case "$goarch" in i386) goarch='386' ;; arm32v*) variant="${goarch#arm32}"; goarch='arm' ;; arm64v*) variant="${goarch#arm64}"; goarch='arm64' ;; esac jq -n --arg os "$goos" --arg arch "$goarch" --arg variant "$variant" '{ "architecture": $arch, "os": $os } + if ($variant | length) > 0 then { "variant": $variant } else null end' } manifest_list_items() { local arch="$1"; shift local repo="$1"; shift local digest="$1"; shift local manifest schemaVersion manifest="$(fetch_manifest "$repo" "$digest")" || return 1 schemaVersion="$(jq -r '.schemaVersion' <<<"$manifest")" || return 1 local platform platform="$(arch_to_platform "$arch")" || return 1 local mediaType case "$schemaVersion" in 2) mediaType="$(jq -r '.mediaType' <<<"$manifest")" || return 1 if [ "$mediaType" = 'application/vnd.docker.distribution.manifest.list.v2+json' ]; then # if we're already a manifest list, we've scored -- just return the list items directly! jq '.manifests' <<<"$manifest" || return 1 return 0 fi if [ "$mediaType" != 'application/vnd.docker.distribution.manifest.v2+json' ]; then echo >&2 "error: unknown schemaVersion 2 'mediaType' value: '$mediaType' (while parsing '$repo@$digest' for '$arch')" exit 1 fi local os; os="$(jq -r '.os' <<<"$platform")" if [ "$os" = 'windows' ]; then # if this is a Windows image, we need to fetch "os.version" from the image config object for the "platform" output local config configDigest configDigest="$(jq -r '.config.digest' <<<"$manifest")" config="$(fetch_blob "$repo" "$configDigest")" local osVersion osVersion="$(jq -r '."os.version"' <<<"$config")" if [ -n "$osVersion" ]; then platform="$(jq -n --argjson 'platform' "$platform" --arg 'osVersion' "$osVersion" '$platform + { "os.version": $osVersion }')" fi fi ;; 1) mediaType='application/vnd.docker.distribution.manifest.v1+json' # TODO ??? (especially "os.version" for a Windows-based image?) ;; *) echo >&2 "error: unknown 'schemaVersion' value: '$schemaVersion' (while parsing '$repo@$digest' for '$arch')" exit 1 ;; esac jq -n --arg 'mediaType' "$mediaType" --argjson 'size' "${#manifest}" --arg 'digest' "$digest" --argjson 'platform' "$platform" '[ { "mediaType": $mediaType, "size": $size, "digest": $digest, "platform": $platform } ]' } for img; do split_img "$img" 'repo' 'tag' img="$repo:$tag" echo echo "$targetOrg/$img" arches="$(bashbrew cat --format '{{ range .Entries }}{{ .Architectures | join "\n" }}{{ "\n" }}{{ end }}' "$img")" arches="$(sort -u <<<"$arches")" arches="$(xargs <<<"$arches")" manifests='[]' for arch in $arches; do ns="$(arch_to_ns "$arch")" digest="$(fetch_digest "$ns/$repo" "$tag")" || continue # if the tag doesn't exist in this repo, skip it echo " - $arch: $digest" manifestListItems="$(manifest_list_items "$arch" "$ns/$repo" "$digest")" manifests="$(jq -n --argjson 'manifests' "$manifests" --argjson new "$manifestListItems" '$manifests + $new')" done newManifestList="$(jq -n --argjson 'manifests' "$manifests" '{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "manifests": $manifests }')" oldManifestList="$(oldDigest="$(fetch_digest "$targetOrg/$repo" "$tag")" && fetch_manifest "$targetOrg/$repo" "$oldDigest")" || : diff -u <(jq --tab . <<<"$oldManifestList") <(jq --tab . <<<"$newManifestList") || : # TODO opportunistic blob mounts + manifest list push #echo "==> $(jq -c . <<<"$newManifestList")" done # TODO rewrite this all in Perl using promises so it can parallelize like crazy