Skip to content

Instantly share code, notes, and snippets.

@zxkane
Last active August 19, 2023 01:31
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save zxkane/23de226fee8806ee0ed8c05136972ce0 to your computer and use it in GitHub Desktop.
Save zxkane/23de226fee8806ee0ed8c05136972ce0 to your computer and use it in GitHub Desktop.
get size of layers of docker image. Inspired by this post(https://ops.tips/blog/inspecting-docker-image-without-pull/).
#!/bin/bash
set -o errexit
source ./library
# Entry point of the script.
# If makes sure that the user supplied the right
# amount of arguments (image_name)
# and then performs the main workflow:
# 1. retrieve the image digest
# 2. retrieve the layer info of image
# with all tags.
main() {
check_args "$@"
local image=$1
local tags=$(get_tags $image)
iterate_image_tags_layers "$image" "$tags"
}
# Makes sure that we provided (from the cli)
# enough arguments.
check_args() {
if (($# != 1)); then
echo "Error:
Two arguments must be provided - $# provided.
Usage:
$0 <image>
Aborting."
exit 1
fi
}
iterate_image_tags_layers() {
local image=$1
local tags=$2
echo "Iterate the tags of image.
IMAGE: $image
" >&2
# for tag in $(jq -r '.tags | limit(10;.[])' <<< "$tags"); do
for tag in $(jq -r '.tags | .[]' <<< "$tags"); do
manifests=$(get_image_tag_layers $image $tag)
formatOutput "$image" "$tag" "$manifests"
done
}
# Run the entry point with the CLI arguments
# as a list of words as supplied.
main "$@"
#!/bin/bash
set -e
source ./library
# Entry point of the script.
# If makes sure that the user supplied the right
# amount of arguments (image_name and image_tag)
# and then performs the main workflow:
# 1. retrieve the image digest
# 2. retrieve the size of layers for
# that image.
main() {
check_args "$@"
local image=$1
local tag=$2
local manifests=$(get_image_tag_layers $image $tag)
formatOutput "$image" "$tag" "$manifests"
totalSize "$image" "$tag" "$manifests"
}
# Makes sure that we provided (from the cli)
# enough arguments.
check_args() {
if (($# != 2)); then
echo "Error:
Two arguments must be provided - $# provided.
Usage:
$0 <image> <tag>
Aborting."
exit 1
fi
}
totalSize(){
local image=$1
local tag=$2
local manifests=$3
echo "Calculate the total size of image.
IMAGE: $image
TAG: $tag
" >&2
for manifest in $(jq -r '.[] | @text' <<<"$manifests"); do
local platform=$(jq -r '.platform | @text' <<< "$manifest")
local digest=$(jq -r '.digest' <<< "$manifest")
local layers=$(jq -r '.layers' <<< "$manifest")
echo " Digest: $digest
Platform: $platform
TOTAL_SIZE: $(jq -r 'reduce (.[]|.size) as $item (0; . + $item)' <<< "$layers")
" >&2
done
}
# Run the entry point with the CLI arguments
# as a list of words as supplied.
main "$@"
#!/bin/bash
# Address of the registry that we'll be
# performing the inspections against.
# This is necessary as the arguments we
# supply to the API calls don't include
# such address (the address is used in the
# url itself).
readonly REGISTRY_ADDRESS="${REGISTRY_ADDRESS:-https://registry-1.docker.io}"
readonly REGISTRY_AUTH_ADDRESS="${REGISTRY_AUTH_ADDRESS:-https://auth.docker.io}"
# Retrieve the layers of image with manfiest info.
# note.: $image must be the full image name without
# the registry part, e.g.: `nginx` should
# be named `library/nginx`.
get_image_tag_layers() {
local image=$1
local tag=$2
manifest_lists=$(get_manifest_list $image $tag)
local SPE=""
echo "["
for manifest in $(jq -r '.[] | @text' <<< "$manifest_lists"); do
local platform=$(jq -r '.platform' <<< "$manifest")
local digest=$(jq -r '.digest' <<< "$manifest")
echo $SPE
echo "{
\"platform\": $platform,
\"layers\": $(get_layers_by_digest $image $digest),
\"digest\": \"$digest\"
}"
SPE=","
done
echo "]"
}
get_layers_by_digest() {
local image=$1
local digest=$2
echo "Retrieving image layers.
IMAGE: $image
Digest: $digest
" >&2
manifest=$(curl \
--silent -m 10 --retry 10 \
--header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
--header "Authorization: Bearer $(get_token $image)" \
"$REGISTRY_ADDRESS/v2/$image/manifests/$digest")
schemaVersion=$(jq -r '.schemaVersion' <<< "$manifest")
if [ "$schemaVersion" == "2" ]; then
jq -r '.layers' <<< "$manifest"
else
local SPE=""
echo "["
for blob in $(jq -r '.fsLayers | .[] | .blobSum' <<< "$manifest"); do
echo $SPE
echo "{ \"digest\": \"$blob\", \"size\": $(get_blob_size $image $blob)}"
SPE=","
done
echo "]"
fi
}
get_manifest_list() {
local image=$1
local tag=$2
echo "Retrieving image manifest list.
IMAGE: $image
TAG: $tag
" >&2
manifestOutput=$(curl \
--silent -m 10 --retry 10 \
--header "Accept: application/vnd.docker.distribution.manifest.list.v2+json" \
--header "Authorization: Bearer $(get_token $image)" \
"$REGISTRY_ADDRESS/v2/$image/manifests/$tag")
manifests=$(jq -r 'if .manifests != null then .manifests else .schemaVersion end' <<<"$manifestOutput")
if [ "$manifests" == "1" ]; then
echo "[ {
\"platform\": {
\"architecture\": \"$(jq -r '.architecture' <<<"$manifestOutput")\",
\"os\": \"linux\"
},
\"digest\": \"$tag\"
}
]"
else
echo "$manifests"
fi
}
# Retrieve the size of blob.
get_blob_size(){
local image=$1
local blob=$2
echo "Retrieving blob size.
IMAGE: $image
Blob: $blob
" >&2
blobLocation=$(curl \
--silent -I -m 10 --retry 10 \
--header "Authorization: Bearer $(get_token $image)" \
"$REGISTRY_ADDRESS/v2/$image/blobs/$blob" \
| tr -d '\r' | sed -En 's/^[Ll]ocation: (.*)/\1/p')
curl \
--silent -I -m 10 --retry 5 \
"$blobLocation" \
| tr -d '\r' | sed -En 's/^[Cc]ontent-[Ll]ength: (.*)/\1/p'
}
#Refresh current token str if necessary.
refresh_token_str() {
local image=$1
local current=${2:-''}
if [ -z "$current" ]; then
echo "Retrieving Docker Hub token.
IMAGE: $image
" >&2
curl \
--silent -m 10 --retry 5 \
"$REGISTRY_AUTH_ADDRESS/token?scope=repository:$image:pull&service=registry.docker.io"
else
issuedAt=$(jq -re '.issued_at' <<< "$current")
expiresIn=$(jq -re '.expires_in' <<< "$current")
expiresTimestamp=$(date -d "$issuedAt +$(($expiresIn - 30)) seconds" +'%s')
nowTimestamp=$(date +'%s')
if [ $nowTimestamp -ge $expiresTimestamp ]; then
refresh_token_str "$image"
else
echo "Reusing current Docker Hub token.
IMAGE: $image
" >&2
echo "$current"
fi
fi
}
TOKEN_FILE=$(mktemp)
remove_token_files() {
rm $TOKEN_FILE
}
trap remove_token_files EXIT
# Retrieves a token that grants access to the
# registry.docker.io (or any docker registry) access
# to pull a specific image.
#
# note.: the token that we retrieve is valid only
# for that image.
# note.: we get the token from `auth.docker.io` and
# not `registry.docker.io`.
# usage.: get_token "library/nginx"
#
get_token(){
local image=$1
local token_str=$(head -n 1 $TOKEN_FILE)
token_str=$(refresh_token_str "$image" "$token_str")
echo "$token_str" > "$TOKEN_FILE"
jq -re '.token' <<< "$token_str"
}
get_tags() {
local image=$1
echo "Retrieving image tags.
IMAGE: $image
" >&2
curl \
--silent -m 10 --retry 10 \
--header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
--header "Authorization: Bearer $(get_token $image)" \
"$REGISTRY_ADDRESS/v2/$image/tags/list"
}
formatOutput() {
local image=$1
local tag=$2
local manifests=$3
echo "Format the manifests output.
IMAGE: $image
TAG: $tag
" >&2
for manifest in $(jq -r '.[] | @text' <<<"$manifests"); do
local platformArch=$(jq -r '.platform.architecture' <<< "$manifest")
local platformOs=$(jq -r '.platform.os' <<< "$manifest")
local platformVariant=$(jq -r '.platform.variant' <<< "$manifest")
local platformVersion=$(jq -r '.platform["os.version"]' <<< "$manifest")
local digest=$(jq -r '.digest' <<< "$manifest")
local layers=$(jq -r '.layers' <<< "$manifest")
layersArray=$(jq -r '.[] | [.digest, .size] | @csv ' <<< "$layers")
if [ -n "$layersArray" ]; then
for k in $layersArray; do
echo "$tag,$digest,$platformArch,$platformOs,$platformVariant,$platformVersion,$k"
done
else
echo "$tag,unknown,unknown,unknown,unknown,unknown,unknown,0"
fi
done
}
@zxkane
Copy link
Author

zxkane commented Apr 29, 2020

Usage:

# Fetch all layers info from a image with all tags
./image-all-tags-layers.sh library/nginx

# Fetch a image with specific tag
./image-layers-size.sh library/nginx 1.13.10-alpine-perl

@wu105
Copy link

wu105 commented May 27, 2020

Got it to work with harbor with modification to the library:

Usage:

Fetch all layers info from a image with all tags

REGISTRY_AUTH_ADDRESS=https://harbor.some.com/service REGISTRY_ADDRESS=https://harbor.some.com REGISTRY_SERVICE=harbor-registry image-all-tabs-layers.sh proj/imagename

Fetch a image with specific tag

REGISTRY_AUTH_ADDRESS=https://harbor.some.com/service REGISTRY_ADDRESS=https://harbor.some.com REGISTRY_SERVICE=harbor-registry image-layers-size.sh proj/imagename tag

Modification to library:

Add REGISTRY_SERVICE, extract registry account/password from default ~/.docker/config.json, both used in obtaining token

diff library.~1~ library
10a11,18
> readonly REGISTRY_SERVICE="${REGISTRY_SERVICE:-registry.docker.io}"
> 
> REGISTRY=${REGISTRY_ADDRESS##*/}
> REGISTRY_AUTH=$(jq -r '.auths."'$REGISTRY'".auth' ~/.docker/config.json)
> if [ -n "$REGISTRY_AUTH" ]; then
>     REGISTRY_AUTH_DECODED=$(base64 -d <<< $REGISTRY_AUTH)
>     REGISTRY_TOKEN_AUTH="-u '$REGISTRY_AUTH_DECODED'"
> fi
131c139
<     curl \
---
>     eval curl $REGISTRY_TOKEN_AUTH \
133c141
<       "$REGISTRY_AUTH_ADDRESS/token?scope=repository:$image:pull&service=registry.docker.io"
---
>       "$REGISTRY_AUTH_ADDRESS/token?scope=repository:$image:pull\\&service=$REGISTRY_SERVICE"

@aleon1220
Copy link

This is good and thanks but honestly i chose to go with Dive

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment