Skip to content

Instantly share code, notes, and snippets.

@Eliastik
Last active July 13, 2024 12:26
Show Gist options
  • Save Eliastik/38e391183c137442403e4dc46d63ed26 to your computer and use it in GitHub Desktop.
Save Eliastik/38e391183c137442403e4dc46d63ed26 to your computer and use it in GitHub Desktop.
This script checks for updates of the images for current installed Docker containers.
#!/bin/bash
# Filename: check-updates-docker.sh
#
# Author: Eliastik ( eliastiksofts.com/contact )
# Version 1.3 (13 july 2024) - Eliastik
#
# Description: This script checks for updates of the images for current installed Docker containers.
# This script doesn't need root access as long as you are running the Docker daemon in rootless mode
# or you followed the "Manage Docker as a non-root user" in the Docker documentation: https://docs.docker.com/engine/install/linux-postinstall/
#
# Changelog:
# Version 1.3 (13 july 2024):
# - Check outdated container version EOL with endoflife.date API
# Version 1.2.1 (25 march 2024):
# - Added support for Gitlab Registry
# - Allow to disable update checking for certain containers, by adding the label value "org.eliastik.checkUpdatesDocker.disabled" in the container configuration to "True"
# - Minor fixes
# Version 1.2 (24 march 2024):
# - Enable version checking for images with tags ending with for example "-alpine" or other names
# - Enable version detection for images from Github Packages and Google Container Registry
# Version 1.1 (15 april 2023):
# - By default, the command only checks for patch versions upgrades. To enable checking for major/minor version
# you have to run the command with -mm or -m argument
# - Added help for the command
#
# Version 1.0 (17 march 2023):
# - Initial version
verbose=false
ultra_verbose=false
enable_major_versions=false
enable_minor_versions=false
tag_matching_regexp="^(v|[0-9\.])+(-[a-zA-Z]*)?$"
# Parse arguments on command line
for argument in "$@"
do
if [[ "$argument" = "-v" ]] || [[ "$argument" = "--verbose" ]]; then
verbose=true
fi
if [[ "$argument" = "-vvv" ]] || [[ "$argumenet" = "--ultra-verbose" ]]; then
verbose=true
ultra_verbose=true
fi
if [[ "$argument" = "-mm" ]] || [[ "$argument" = "--major" ]]; then
enable_major_versions=true
enable_minor_versions=true
fi
if [[ "$argument" = "-m" ]] || [[ "$argument" = "--minor" ]]; then
enable_minor_versions=true
fi
if [[ "$argument" = "-h" ]] || [[ "$argument" = "--help" ]]; then
name=$(basename "$0")
echo "Check updates for your Docker containers - by Eliastik (eliastiksofts.com)"
echo
echo "Syntax: $name [-v] [-vvv] [-mm] [-m]"
echo "options:"
echo "v (--verbose) Output more verboses messages when running the command"
echo "vvv (--ultra-verbose) Output debug messages"
echo "mm (--major) Enable major/minor versions checking"
echo "m (--minor) Enable minor versions checking"
echo
echo "Note: by default the command only checks for patch versions upgrades"
exit 0
fi
done
# Get all containers currently running on the system
containers=$(docker container list --format '{{.ID}}')
# For each container
for container in $containers; do
# Get full image name
image=$(docker inspect --format='{{.Config.Image}}' "$container")
# Get the update checking disabling configuration value
check_disabled=$(docker inspect --format='{{ index .Config.Labels "org.eliastik.checkUpdatesDocker.disabled"}}' "$container")
# Get image name
image_name=$(echo "$image" | cut -d ':' -f1)
# Get image tag
image_tag=$(echo "$image" | cut -d ':' -f2)
# Get image flavour (for example "alpine")
image_flavour=$(echo "$image_tag" | rev | cut -s -d '-' -f 1 | rev)
if [[ "$image_name" != */* ]]; then
image_name="library/$image_name"
fi
# If the update checking is disabled for the current container, we skip the checking
if [[ "$check_disabled" = "True" ]]; then
if [[ "$ultra_verbose" = true ]]; then
echo "The image $image_name was ignored, because the update version checking was disabled for the container"
fi
continue
fi
# If the image tag is not a version number
if [[ ! "$image_tag" =~ $tag_matching_regexp ]]; then
if [[ "$ultra_verbose" = true ]]; then
echo "The image $image_name was ignored, because its version contains something other than numbers and dots ($image_tag)"
fi
continue
fi
# Check the most recent image version using the Github Packages API, Gitlab Registry API, Google Container Registry API or Docker Hub API
# Assuming the image is public
if [[ "$image_name" == ghcr.io* ]]; then
# Github Packages API
image_name_github_package=${image_name#ghcr.io/}
token=$(curl -s "https://ghcr.io/token?scope=repository:$image_name_github_package:pull" | jq -r '.token')
url=$(echo "https://ghcr.io/v2/$image_name_github_package/tags/list")
latest_tag_curl_head=200 # HEAD is forbidden for this API
elif [[ "$image_name" == registry.gitlab.com* ]]; then
# Gitlab Registry API
image_name_gitlab_registry=${image_name#registry.gitlab.com/}
token=$(curl -s "https://gitlab.com/jwt/auth?scope=repository:${image_name_gitlab_registry}:pull&service=container_registry" | jq -r '.token')
url=$(echo "https://registry.gitlab.com/v2/$image_name_gitlab_registry/tags/list")
latest_tag_curl_head=200 # HEAD is forbidden for this API
elif [[ "$image_name" == gcr.io* ]]; then
# Google Container Registry API
image_name_google_registry=${image_name#gcr.io/}
url=$(echo "https://gcr.io/v2/$image_name_google_registry/tags/list")
latest_tag_curl_head=200 # HEAD is forbidden for this API
else
# Docker Hub API
url=$(echo "https://registry.hub.docker.com/v2/repositories/$image_name/tags/?page_size=100")
latest_tag_curl_head=$(curl -s -o /dev/null -I -w "%{http_code}" "$url") # HEAD request
fi
# If the API returns an error response
if [[ "$latest_tag_curl_head" != 200 ]]; then
if [[ "$ultra_verbose" = true ]]; then
echo "The image $image_name was ignored, because the request to the Docker API returned an incorrect HTTP response (HTTP code $latest_tag_curl_head)"
fi
continue
fi
# Get all image tags from the APIs
if [[ "$image_name" == ghcr.io* ]] || [[ "$image_name" == registry.gitlab.com* ]]; then
# Github Packages API or Gitlab Registry API
latest_tag_curl=$(curl -s -H "Authorization: Bearer $token" "$url")
latest_tag_json=$(echo "$latest_tag_curl" | jq -r '.tags[]')
elif [[ "$image_name" == gcr.io* ]]; then
# Google Container Registry API
latest_tag_curl=$(curl -s "$url")
latest_tag_json=$(echo "$latest_tag_curl" | jq -r '.manifest | .[].tag[]')
else
# Docker Hub API
latest_tag_curl=$(curl -s "$url")
latest_tag_json=$(echo "$latest_tag_curl" | jq -r '.results[].name')
fi
# If there was an error parsing the JSON response
if [ -z "$latest_tag_json" ]; then
if [[ "$ultra_verbose" = true ]]; then
echo "The image $image_name was ignored, because the request to the Docker API returned an incorrect or empty JSON response"
fi
continue
fi
# Filter the tags based on the current tag of the image (major version)
latest_tag=$(echo "$latest_tag_json" | grep -v -E '^(latest|edge)$' | grep -E "^(v|[0-9\.])+(-${image_flavour})?$")
if [[ "$enable_major_versions" = false ]]; then
if [[ "$enable_minor_versions" = true ]]; then
image_tag_major=$(echo "$image_tag" | cut -d '-' -f 1 | cut -d '.' -f 1)
else
image_tag_major=$(echo "$image_tag" | cut -d '-' -f 1 | cut -d '.' -f 1,2)
fi
# Filter the tags based on the version of the image (minor or patch version)
latest_tag=$(echo "$latest_tag" | grep -E "^(v)?${image_tag_major}\.[0-9\.]+(-${image_flavour})?$")
fi
# Sort latest version tag first
latest_tag=$(echo "$latest_tag" | sort -Vr | head -n1)
# Check if the latest version tag is the same as the image tag ; if not, there is an update available
if [ -n "$latest_tag" ] && [[ "$image_tag" != "$latest_tag" ]]; then
echo "The image $image_name needs to be updated. Current version: $image_tag, most recent version: $latest_tag."
else
if [[ "$verbose" = true ]] || [[ "$ultra_verbose" = true ]]; then
echo "The image $image_name is up to date (version: $image_tag)."
fi
fi
# Call EOL API
image_app_name=$(echo "$image_name" | cut -d '/' -f 2)
version_eol=$(echo "$image_tag" | cut -d '-' -f 1)
if [[ "$version_eol" =~ ^([0-9]+)\.([0-9]+)\.[0-9]+$ ]]; then
version_eol=$(echo "${BASH_REMATCH[1]}.${BASH_REMATCH[2]}")
elif [[ "$version_eol" =~ ^([0-9]+)\.([0-9]+)$ ]]; then
version_eol=$(echo "${BASH_REMATCH[1]}")
fi
eol_api_curl_response=$(curl -s "https://endoflife.date/api/$image_app_name/$version_eol.json" -L)
eol_product_not_found=$(echo "$eol_api_curl_response" | jq -e '.message | select(. != "Product not found")')
if [ "$eol_product_not_found" == "null" ]; then
eol_date=$(echo "$eol_api_curl_response" | jq -r '.eol')
if [ "$eol_date" != "null" ] && [ "$eol_date" != "false" ]; then
current_date=$(date +%Y-%m-%d)
current_date_epoch=$(date -d "$current_date" +%s)
eol_date_epoch=$(date -d "$eol_date" +%s)
if [ "$eol_date_epoch" -lt "$current_date_epoch" ]; then
echo "/!\ The version of the image $image_name ($image_tag) has reached its end of life on $eol_date and will not be updated anymore. Please upgrade the image to a new major version."
fi
fi
else
if [[ "$ultra_verbose" = true ]]; then
echo "Cannot retrieve end of life data for image $image_name."
fi
fi
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment