github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/contrib/download-frozen-image-v2.sh (about) 1 #!/usr/bin/env bash 2 set -eo pipefail 3 4 # hello-world latest ef872312fe1b 3 months ago 910 B 5 # hello-world latest ef872312fe1bbc5e05aae626791a47ee9b032efa8f3bda39cc0be7b56bfe59b9 3 months ago 910 B 6 7 # debian latest f6fab3b798be 10 weeks ago 85.1 MB 8 # debian latest f6fab3b798be3174f45aa1eb731f8182705555f89c9026d8c1ef230cbf8301dd 10 weeks ago 85.1 MB 9 10 if ! command -v curl &> /dev/null; then 11 echo >&2 'error: "curl" not found!' 12 exit 1 13 fi 14 if ! command -v jq &> /dev/null; then 15 echo >&2 'error: "jq" not found!' 16 exit 1 17 fi 18 19 usage() { 20 echo "usage: $0 dir image[:tag][@digest] ..." 21 echo " $0 /tmp/old-hello-world hello-world:latest@sha256:8be990ef2aeb16dbcb9271ddfe2610fa6658d13f6dfb8bc72074cc1ca36966a7" 22 [ -z "$1" ] || exit "$1" 23 } 24 25 dir="$1" # dir for building tar in 26 shift || usage 1 >&2 27 28 [ $# -gt 0 -a "$dir" ] || usage 2 >&2 29 mkdir -p "$dir" 30 31 # hacky workarounds for Bash 3 support (no associative arrays) 32 images=() 33 rm -f "$dir"/tags-*.tmp 34 manifestJsonEntries=() 35 doNotGenerateManifestJson= 36 # repositories[busybox]='"latest": "...", "ubuntu-14.04": "..."' 37 38 # bash v4 on Windows CI requires CRLF separator 39 newlineIFS=$'\n' 40 if [ "$(go env GOHOSTOS)" = 'windows' ]; then 41 major=$(echo ${BASH_VERSION%%[^0.9]} | cut -d. -f1) 42 if [ "$major" -ge 4 ]; then 43 newlineIFS=$'\r\n' 44 fi 45 fi 46 47 registryBase='https://registry-1.docker.io' 48 authBase='https://auth.docker.io' 49 authService='registry.docker.io' 50 51 # https://github.com/moby/moby/issues/33700 52 fetch_blob() { 53 local token="$1"; shift 54 local image="$1"; shift 55 local digest="$1"; shift 56 local targetFile="$1"; shift 57 local curlArgs=( "$@" ) 58 59 local curlHeaders="$( 60 curl -S "${curlArgs[@]}" \ 61 -H "Authorization: Bearer $token" \ 62 "$registryBase/v2/$image/blobs/$digest" \ 63 -o "$targetFile" \ 64 -D- 65 )" 66 curlHeaders="$(echo "$curlHeaders" | tr -d '\r')" 67 if echo "$curlHeaders" | grep -qE "^HTTP/[0-9].[0-9] 3"; then 68 rm -f "$targetFile" 69 70 local blobRedirect="$(echo "$curlHeaders" | awk -F ': ' 'tolower($1) == "location" { print $2; exit }')" 71 if [ -z "$blobRedirect" ]; then 72 echo >&2 "error: failed fetching '$image' blob '$digest'" 73 echo "$curlHeaders" | head -1 >&2 74 return 1 75 fi 76 77 curl -fSL "${curlArgs[@]}" \ 78 "$blobRedirect" \ 79 -o "$targetFile" 80 fi 81 } 82 83 while [ $# -gt 0 ]; do 84 imageTag="$1" 85 shift 86 image="${imageTag%%[:@]*}" 87 imageTag="${imageTag#*:}" 88 digest="${imageTag##*@}" 89 tag="${imageTag%%@*}" 90 91 # add prefix library if passed official image 92 if [[ "$image" != *"/"* ]]; then 93 image="library/$image" 94 fi 95 96 imageFile="${image//\//_}" # "/" can't be in filenames :) 97 98 token="$(curl -fsSL "$authBase/token?service=$authService&scope=repository:$image:pull" | jq --raw-output '.token')" 99 100 manifestJson="$( 101 curl -fsSL \ 102 -H "Authorization: Bearer $token" \ 103 -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \ 104 -H 'Accept: application/vnd.docker.distribution.manifest.v1+json' \ 105 "$registryBase/v2/$image/manifests/$digest" 106 )" 107 if [ "${manifestJson:0:1}" != '{' ]; then 108 echo >&2 "error: /v2/$image/manifests/$digest returned something unexpected:" 109 echo >&2 " $manifestJson" 110 exit 1 111 fi 112 113 imageIdentifier="$image:$tag@$digest" 114 115 schemaVersion="$(echo "$manifestJson" | jq --raw-output '.schemaVersion')" 116 case "$schemaVersion" in 117 2) 118 mediaType="$(echo "$manifestJson" | jq --raw-output '.mediaType')" 119 120 case "$mediaType" in 121 application/vnd.docker.distribution.manifest.v2+json) 122 configDigest="$(echo "$manifestJson" | jq --raw-output '.config.digest')" 123 imageId="${configDigest#*:}" # strip off "sha256:" 124 125 configFile="$imageId.json" 126 fetch_blob "$token" "$image" "$configDigest" "$dir/$configFile" -s 127 128 layersFs="$(echo "$manifestJson" | jq --raw-output --compact-output '.layers[]')" 129 IFS="$newlineIFS" 130 layers=( $layersFs ) 131 unset IFS 132 133 echo "Downloading '$imageIdentifier' (${#layers[@]} layers)..." 134 layerId= 135 layerFiles=() 136 for i in "${!layers[@]}"; do 137 layerMeta="${layers[$i]}" 138 139 layerMediaType="$(echo "$layerMeta" | jq --raw-output '.mediaType')" 140 layerDigest="$(echo "$layerMeta" | jq --raw-output '.digest')" 141 142 # save the previous layer's ID 143 parentId="$layerId" 144 # create a new fake layer ID based on this layer's digest and the previous layer's fake ID 145 layerId="$(echo "$parentId"$'\n'"$layerDigest" | sha256sum | cut -d' ' -f1)" 146 # this accounts for the possibility that an image contains the same layer twice (and thus has a duplicate digest value) 147 148 mkdir -p "$dir/$layerId" 149 echo '1.0' > "$dir/$layerId/VERSION" 150 151 if [ ! -s "$dir/$layerId/json" ]; then 152 parentJson="$(printf ', parent: "%s"' "$parentId")" 153 addJson="$(printf '{ id: "%s"%s }' "$layerId" "${parentId:+$parentJson}")" 154 # this starter JSON is taken directly from Docker's own "docker save" output for unimportant layers 155 jq "$addJson + ." > "$dir/$layerId/json" <<-'EOJSON' 156 { 157 "created": "0001-01-01T00:00:00Z", 158 "container_config": { 159 "Hostname": "", 160 "Domainname": "", 161 "User": "", 162 "AttachStdin": false, 163 "AttachStdout": false, 164 "AttachStderr": false, 165 "Tty": false, 166 "OpenStdin": false, 167 "StdinOnce": false, 168 "Env": null, 169 "Cmd": null, 170 "Image": "", 171 "Volumes": null, 172 "WorkingDir": "", 173 "Entrypoint": null, 174 "OnBuild": null, 175 "Labels": null 176 } 177 } 178 EOJSON 179 fi 180 181 case "$layerMediaType" in 182 application/vnd.docker.image.rootfs.diff.tar.gzip) 183 layerTar="$layerId/layer.tar" 184 layerFiles=( "${layerFiles[@]}" "$layerTar" ) 185 # TODO figure out why "-C -" doesn't work here 186 # "curl: (33) HTTP server doesn't seem to support byte ranges. Cannot resume." 187 # "HTTP/1.1 416 Requested Range Not Satisfiable" 188 if [ -f "$dir/$layerTar" ]; then 189 # TODO hackpatch for no -C support :'( 190 echo "skipping existing ${layerId:0:12}" 191 continue 192 fi 193 token="$(curl -fsSL "$authBase/token?service=$authService&scope=repository:$image:pull" | jq --raw-output '.token')" 194 fetch_blob "$token" "$image" "$layerDigest" "$dir/$layerTar" --progress 195 ;; 196 197 *) 198 echo >&2 "error: unknown layer mediaType ($imageIdentifier, $layerDigest): '$layerMediaType'" 199 exit 1 200 ;; 201 esac 202 done 203 204 # change "$imageId" to be the ID of the last layer we added (needed for old-style "repositories" file which is created later -- specifically for older Docker daemons) 205 imageId="$layerId" 206 207 # munge the top layer image manifest to have the appropriate image configuration for older daemons 208 imageOldConfig="$(jq --raw-output --compact-output '{ id: .id } + if .parent then { parent: .parent } else {} end' "$dir/$imageId/json")" 209 jq --raw-output "$imageOldConfig + del(.history, .rootfs)" "$dir/$configFile" > "$dir/$imageId/json" 210 211 manifestJsonEntry="$( 212 echo '{}' | jq --raw-output '. + { 213 Config: "'"$configFile"'", 214 RepoTags: ["'"${image#library\/}:$tag"'"], 215 Layers: '"$(echo '[]' | jq --raw-output ".$(for layerFile in "${layerFiles[@]}"; do echo " + [ \"$layerFile\" ]"; done)")"' 216 }' 217 )" 218 manifestJsonEntries=( "${manifestJsonEntries[@]}" "$manifestJsonEntry" ) 219 ;; 220 221 *) 222 echo >&2 "error: unknown manifest mediaType ($imageIdentifier): '$mediaType'" 223 exit 1 224 ;; 225 esac 226 ;; 227 228 1) 229 if [ -z "$doNotGenerateManifestJson" ]; then 230 echo >&2 "warning: '$imageIdentifier' uses schemaVersion '$schemaVersion'" 231 echo >&2 " this script cannot (currently) recreate the 'image config' to put in a 'manifest.json' (thus any schemaVersion 2+ images will be imported in the old way, and their 'docker history' will suffer)" 232 echo >&2 233 doNotGenerateManifestJson=1 234 fi 235 236 layersFs="$(echo "$manifestJson" | jq --raw-output '.fsLayers | .[] | .blobSum')" 237 IFS="$newlineIFS" 238 layers=( $layersFs ) 239 unset IFS 240 241 history="$(echo "$manifestJson" | jq '.history | [.[] | .v1Compatibility]')" 242 imageId="$(echo "$history" | jq --raw-output '.[0]' | jq --raw-output '.id')" 243 244 echo "Downloading '$imageIdentifier' (${#layers[@]} layers)..." 245 for i in "${!layers[@]}"; do 246 imageJson="$(echo "$history" | jq --raw-output ".[${i}]")" 247 layerId="$(echo "$imageJson" | jq --raw-output '.id')" 248 imageLayer="${layers[$i]}" 249 250 mkdir -p "$dir/$layerId" 251 echo '1.0' > "$dir/$layerId/VERSION" 252 253 echo "$imageJson" > "$dir/$layerId/json" 254 255 # TODO figure out why "-C -" doesn't work here 256 # "curl: (33) HTTP server doesn't seem to support byte ranges. Cannot resume." 257 # "HTTP/1.1 416 Requested Range Not Satisfiable" 258 if [ -f "$dir/$layerId/layer.tar" ]; then 259 # TODO hackpatch for no -C support :'( 260 echo "skipping existing ${layerId:0:12}" 261 continue 262 fi 263 token="$(curl -fsSL "$authBase/token?service=$authService&scope=repository:$image:pull" | jq --raw-output '.token')" 264 fetch_blob "$token" "$image" "$imageLayer" "$dir/$layerId/layer.tar" --progress 265 done 266 ;; 267 268 *) 269 echo >&2 "error: unknown manifest schemaVersion ($imageIdentifier): '$schemaVersion'" 270 exit 1 271 ;; 272 esac 273 274 echo 275 276 if [ -s "$dir/tags-$imageFile.tmp" ]; then 277 echo -n ', ' >> "$dir/tags-$imageFile.tmp" 278 else 279 images=( "${images[@]}" "$image" ) 280 fi 281 echo -n '"'"$tag"'": "'"$imageId"'"' >> "$dir/tags-$imageFile.tmp" 282 done 283 284 echo -n '{' > "$dir/repositories" 285 firstImage=1 286 for image in "${images[@]}"; do 287 imageFile="${image//\//_}" # "/" can't be in filenames :) 288 image="${image#library\/}" 289 290 [ "$firstImage" ] || echo -n ',' >> "$dir/repositories" 291 firstImage= 292 echo -n $'\n\t' >> "$dir/repositories" 293 echo -n '"'"$image"'": { '"$(cat "$dir/tags-$imageFile.tmp")"' }' >> "$dir/repositories" 294 done 295 echo -n $'\n}\n' >> "$dir/repositories" 296 297 rm -f "$dir"/tags-*.tmp 298 299 if [ -z "$doNotGenerateManifestJson" ] && [ "${#manifestJsonEntries[@]}" -gt 0 ]; then 300 echo '[]' | jq --raw-output ".$(for entry in "${manifestJsonEntries[@]}"; do echo " + [ $entry ]"; done)" > "$dir/manifest.json" 301 else 302 rm -f "$dir/manifest.json" 303 fi 304 305 echo "Download of images into '$dir' complete." 306 echo "Use something like the following to load the result into a Docker daemon:" 307 echo " tar -cC '$dir' . | docker load"