Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sethbergman/9932fb8c37559683cfd295bc9c07bc16 to your computer and use it in GitHub Desktop.
Save sethbergman/9932fb8c37559683cfd295bc9c07bc16 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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment