Last active
July 13, 2024 12:26
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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