Currently we only support 'application/vnd.docker.distribution.manifest.v2+json'
manifest images download, with more multi-arch images used, we need to support
download images with 'application/vnd.docker.distribution.manifest.list.v2+json'
format(aka "fat manifest"), else we will fail to download those multi-arch ones.
This PR adds 'application/vnd.docker.distribution.manifest.list.v2+json' manifest
support, thus we can download both multi-arch and legacy images.
Signed-off-by: Dennis Chen <dennis.chen@arm.com>
| ... | ... |
@@ -6,7 +6,6 @@ set -eo pipefail |
| 6 | 6 |
|
| 7 | 7 |
# debian latest f6fab3b798be 10 weeks ago 85.1 MB |
| 8 | 8 |
# debian latest f6fab3b798be3174f45aa1eb731f8182705555f89c9026d8c1ef230cbf8301dd 10 weeks ago 85.1 MB |
| 9 |
- |
|
| 10 | 9 |
if ! command -v curl &> /dev/null; then |
| 11 | 10 |
echo >&2 'error: "curl" not found!' |
| 12 | 11 |
exit 1 |
| ... | ... |
@@ -80,6 +79,109 @@ fetch_blob() {
|
| 80 | 80 |
fi |
| 81 | 81 |
} |
| 82 | 82 |
|
| 83 |
+# handle 'application/vnd.docker.distribution.manifest.v2+json' manifest |
|
| 84 |
+handle_single_manifest_v2() {
|
|
| 85 |
+ local manifestJson="$1"; shift |
|
| 86 |
+ |
|
| 87 |
+ local configDigest="$(echo "$manifestJson" | jq --raw-output '.config.digest')" |
|
| 88 |
+ local imageId="${configDigest#*:}" # strip off "sha256:"
|
|
| 89 |
+ |
|
| 90 |
+ local configFile="$imageId.json" |
|
| 91 |
+ fetch_blob "$token" "$image" "$configDigest" "$dir/$configFile" -s |
|
| 92 |
+ |
|
| 93 |
+ local layersFs="$(echo "$manifestJson" | jq --raw-output --compact-output '.layers[]')" |
|
| 94 |
+ local IFS="$newlineIFS" |
|
| 95 |
+ local layers=( $layersFs ) |
|
| 96 |
+ unset IFS |
|
| 97 |
+ |
|
| 98 |
+ echo "Downloading '$imageIdentifier' (${#layers[@]} layers)..."
|
|
| 99 |
+ local layerId= |
|
| 100 |
+ local layerFiles=() |
|
| 101 |
+ for i in "${!layers[@]}"; do
|
|
| 102 |
+ local layerMeta="${layers[$i]}"
|
|
| 103 |
+ |
|
| 104 |
+ local layerMediaType="$(echo "$layerMeta" | jq --raw-output '.mediaType')" |
|
| 105 |
+ local layerDigest="$(echo "$layerMeta" | jq --raw-output '.digest')" |
|
| 106 |
+ |
|
| 107 |
+ # save the previous layer's ID |
|
| 108 |
+ local parentId="$layerId" |
|
| 109 |
+ # create a new fake layer ID based on this layer's digest and the previous layer's fake ID |
|
| 110 |
+ layerId="$(echo "$parentId"$'\n'"$layerDigest" | sha256sum | cut -d' ' -f1)" |
|
| 111 |
+ # this accounts for the possibility that an image contains the same layer twice (and thus has a duplicate digest value) |
|
| 112 |
+ |
|
| 113 |
+ mkdir -p "$dir/$layerId" |
|
| 114 |
+ echo '1.0' > "$dir/$layerId/VERSION" |
|
| 115 |
+ |
|
| 116 |
+ if [ ! -s "$dir/$layerId/json" ]; then |
|
| 117 |
+ local parentJson="$(printf ', parent: "%s"' "$parentId")" |
|
| 118 |
+ local addJson="$(printf '{ id: "%s"%s }' "$layerId" "${parentId:+$parentJson}")"
|
|
| 119 |
+ # this starter JSON is taken directly from Docker's own "docker save" output for unimportant layers |
|
| 120 |
+ jq "$addJson + ." > "$dir/$layerId/json" <<-'EOJSON' |
|
| 121 |
+ {
|
|
| 122 |
+ "created": "0001-01-01T00:00:00Z", |
|
| 123 |
+ "container_config": {
|
|
| 124 |
+ "Hostname": "", |
|
| 125 |
+ "Domainname": "", |
|
| 126 |
+ "User": "", |
|
| 127 |
+ "AttachStdin": false, |
|
| 128 |
+ "AttachStdout": false, |
|
| 129 |
+ "AttachStderr": false, |
|
| 130 |
+ "Tty": false, |
|
| 131 |
+ "OpenStdin": false, |
|
| 132 |
+ "StdinOnce": false, |
|
| 133 |
+ "Env": null, |
|
| 134 |
+ "Cmd": null, |
|
| 135 |
+ "Image": "", |
|
| 136 |
+ "Volumes": null, |
|
| 137 |
+ "WorkingDir": "", |
|
| 138 |
+ "Entrypoint": null, |
|
| 139 |
+ "OnBuild": null, |
|
| 140 |
+ "Labels": null |
|
| 141 |
+ } |
|
| 142 |
+ } |
|
| 143 |
+ EOJSON |
|
| 144 |
+ fi |
|
| 145 |
+ |
|
| 146 |
+ case "$layerMediaType" in |
|
| 147 |
+ application/vnd.docker.image.rootfs.diff.tar.gzip) |
|
| 148 |
+ local layerTar="$layerId/layer.tar" |
|
| 149 |
+ layerFiles=( "${layerFiles[@]}" "$layerTar" )
|
|
| 150 |
+ # TODO figure out why "-C -" doesn't work here |
|
| 151 |
+ # "curl: (33) HTTP server doesn't seem to support byte ranges. Cannot resume." |
|
| 152 |
+ # "HTTP/1.1 416 Requested Range Not Satisfiable" |
|
| 153 |
+ if [ -f "$dir/$layerTar" ]; then |
|
| 154 |
+ # TODO hackpatch for no -C support :'( |
|
| 155 |
+ echo "skipping existing ${layerId:0:12}"
|
|
| 156 |
+ continue |
|
| 157 |
+ fi |
|
| 158 |
+ local token="$(curl -fsSL "$authBase/token?service=$authService&scope=repository:$image:pull" | jq --raw-output '.token')" |
|
| 159 |
+ fetch_blob "$token" "$image" "$layerDigest" "$dir/$layerTar" --progress |
|
| 160 |
+ ;; |
|
| 161 |
+ |
|
| 162 |
+ *) |
|
| 163 |
+ echo >&2 "error: unknown layer mediaType ($imageIdentifier, $layerDigest): '$layerMediaType'" |
|
| 164 |
+ exit 1 |
|
| 165 |
+ ;; |
|
| 166 |
+ esac |
|
| 167 |
+ done |
|
| 168 |
+ |
|
| 169 |
+ # 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) |
|
| 170 |
+ imageId="$layerId" |
|
| 171 |
+ |
|
| 172 |
+ # munge the top layer image manifest to have the appropriate image configuration for older daemons |
|
| 173 |
+ local imageOldConfig="$(jq --raw-output --compact-output '{ id: .id } + if .parent then { parent: .parent } else {} end' "$dir/$imageId/json")"
|
|
| 174 |
+ jq --raw-output "$imageOldConfig + del(.history, .rootfs)" "$dir/$configFile" > "$dir/$imageId/json" |
|
| 175 |
+ |
|
| 176 |
+ local manifestJsonEntry="$( |
|
| 177 |
+ echo '{}' | jq --raw-output '. + {
|
|
| 178 |
+ Config: "'"$configFile"'", |
|
| 179 |
+ RepoTags: ["'"${image#library\/}:$tag"'"],
|
|
| 180 |
+ Layers: '"$(echo '[]' | jq --raw-output ".$(for layerFile in "${layerFiles[@]}"; do echo " + [ \"$layerFile\" ]"; done)")"'
|
|
| 181 |
+ }' |
|
| 182 |
+ )" |
|
| 183 |
+ manifestJsonEntries=( "${manifestJsonEntries[@]}" "$manifestJsonEntry" )
|
|
| 184 |
+} |
|
| 185 |
+ |
|
| 83 | 186 |
while [ $# -gt 0 ]; do |
| 84 | 187 |
imageTag="$1" |
| 85 | 188 |
shift |
| ... | ... |
@@ -101,6 +203,7 @@ while [ $# -gt 0 ]; do |
| 101 | 101 |
curl -fsSL \ |
| 102 | 102 |
-H "Authorization: Bearer $token" \ |
| 103 | 103 |
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \ |
| 104 |
+ -H 'Accept: application/vnd.docker.distribution.manifest.list.v2+json' \ |
|
| 104 | 105 |
-H 'Accept: application/vnd.docker.distribution.manifest.v1+json' \ |
| 105 | 106 |
"$registryBase/v2/$image/manifests/$digest" |
| 106 | 107 |
)" |
| ... | ... |
@@ -119,105 +222,40 @@ while [ $# -gt 0 ]; do |
| 119 | 119 |
|
| 120 | 120 |
case "$mediaType" in |
| 121 | 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[]')" |
|
| 122 |
+ handle_single_manifest_v2 "$manifestJson" |
|
| 123 |
+ ;; |
|
| 124 |
+ application/vnd.docker.distribution.manifest.list.v2+json) |
|
| 125 |
+ layersFs="$(echo "$manifestJson" | jq --raw-output --compact-output '.manifests[]')" |
|
| 129 | 126 |
IFS="$newlineIFS" |
| 130 | 127 |
layers=( $layersFs ) |
| 131 | 128 |
unset IFS |
| 132 | 129 |
|
| 133 |
- echo "Downloading '$imageIdentifier' (${#layers[@]} layers)..."
|
|
| 134 |
- layerId= |
|
| 135 |
- layerFiles=() |
|
| 130 |
+ found="" |
|
| 131 |
+ # parse first level multi-arch manifest |
|
| 136 | 132 |
for i in "${!layers[@]}"; do
|
| 137 | 133 |
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 |
|
| 134 |
+ maniArch="$(echo "$layerMeta" | jq --raw-output '.platform.architecture')" |
|
| 135 |
+ if [ "$maniArch" = "$(go env GOARCH)" ]; then |
|
| 136 |
+ digest="$(echo "$layerMeta" | jq --raw-output '.digest')" |
|
| 137 |
+ # get second level single manifest |
|
| 138 |
+ submanifestJson="$( |
|
| 139 |
+ curl -fsSL \ |
|
| 140 |
+ -H "Authorization: Bearer $token" \ |
|
| 141 |
+ -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \ |
|
| 142 |
+ -H 'Accept: application/vnd.docker.distribution.manifest.list.v2+json' \ |
|
| 143 |
+ -H 'Accept: application/vnd.docker.distribution.manifest.v1+json' \ |
|
| 144 |
+ "$registryBase/v2/$image/manifests/$digest" |
|
| 145 |
+ )" |
|
| 146 |
+ handle_single_manifest_v2 "$submanifestJson" |
|
| 147 |
+ found="found" |
|
| 148 |
+ break |
|
| 179 | 149 |
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 | 150 |
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" )
|
|
| 151 |
+ if [ -z "$found" ]; then |
|
| 152 |
+ echo >&2 "error: manifest for $maniArch is not found" |
|
| 153 |
+ exit 1 |
|
| 154 |
+ fi |
|
| 219 | 155 |
;; |
| 220 |
- |
|
| 221 | 156 |
*) |
| 222 | 157 |
echo >&2 "error: unknown manifest mediaType ($imageIdentifier): '$mediaType'" |
| 223 | 158 |
exit 1 |
| ... | ... |
@@ -64,7 +64,9 @@ case "$PACKAGE_ARCH" in |
| 64 | 64 |
;; |
| 65 | 65 |
*) |
| 66 | 66 |
DOCKERFILE="Dockerfile.$PACKAGE_ARCH" |
| 67 |
- TEST_IMAGE_NAMESPACE="$PACKAGE_ARCH" |
|
| 67 |
+ if [ "$PACKAGE_ARCH" != "aarch64" ]; then |
|
| 68 |
+ TEST_IMAGE_NAMESPACE="$PACKAGE_ARCH" |
|
| 69 |
+ fi |
|
| 68 | 70 |
;; |
| 69 | 71 |
esac |
| 70 | 72 |
export DOCKERFILE TEST_IMAGE_NAMESPACE |