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