From 2f26047f850093374513b78ac2d7579b6f087bd9 Mon Sep 17 00:00:00 2001 From: Jesper Jensen Date: Sun, 28 Dec 2025 11:48:19 +0100 Subject: Add upgrade_docker script --- upgrade_docker | 280 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100755 upgrade_docker (limited to 'upgrade_docker') diff --git a/upgrade_docker b/upgrade_docker new file mode 100755 index 0000000..4e5796c --- /dev/null +++ b/upgrade_docker @@ -0,0 +1,280 @@ +#!/bin/bash +# vim: et +set -Eeuo pipefail + +# @PERF: Currently this script only supports processing a single file at +# a time. Ideally it should probably support multiple files. That would also +# allow it to cache some of the results of fetching new versions instead of +# having to fetch them every time. As it stands right now we could only cache +# that for one file, and the chance that you're using the same image multiple +# times in one docker file seems vanishingly small + +function help { + cat < +Upgrade the image versions in FILE to latest version + +Options: + -v Increase verbosity (repeatable) + -d Dry run (don't apply the updates) + -t, --type Type of file (defaults to DOCKER) + --insecure Disable SSL verification + -h, --help You are looking at it +EOH +} + +function vecho { + LEVEL="$1" + shift + if [[ $VERBOSE -ge $LEVEL ]]; then + echo -e "$@" >&2 + fi +} + +VERBOSE=0 +DRY_RUN=false +POSITIONAL=() +CURL_OPTS="" +TYPE="DOCKER" + +# Read arguments +while [[ "$#" -gt 0 ]]; do + case "$1" in + '-h'|'--help') + help + exit 0 + ;; + '-v') + VERBOSE=$((VERBOSE + 1)) + shift + if [[ "$#" -gt 0 ]] && [[ "$1" =~ ^[0-9]+$ ]]; then + VERBOSE="$1" + shift + fi + ;; + '-d'|'--dry') + shift + TYPE=$1 + shift + ;; + '-t'|'--type') + DRY_RUN=true + shift + ;; + '--insecure') + CURL_OPTS="$CURL_OPTS --insecure" + shift + ;; + *) + POSITIONAL+=("$1") + shift + ;; + esac +done + +if [[ ${#POSITIONAL[@]} -lt 1 ]]; then + echo "Too few arguments" >&2 + help >&2 + exit 1 +fi + +if [[ ${#POSITIONAL[@]} -gt 1 ]]; then + echo "Too many arguments" >&2 + help >&2 + exit 1 +fi + +case "${TYPE^^}" in + 'DOCKER') + TYPE="DOCKER" + ;; + 'KUBE') + TYPE="KUBE" + ;; + *) + echo "Unknown TYPE" + exit 1 + ;; +esac + +FILE="${POSITIONAL[0]}" + +function rel_next { + while IFS= read -r line; do + vecho 2 "Processing link header \"$line\"" + regex="^<([^>]*)>(; ([^=]*)=([^;]*))*$" + if ! [[ $line =~ $regex ]]; then + printf "Could not parse link header %s\n", "$line" >&2 + exit 1 + fi + + link_param_len=$(((${#BASH_REMATCH[@]} - 2) / 3)) + vecho 3 "Link contains $link_param_len params" + + for((i=0; i&2 + exit 1 + fi + + while IFS= read -r line; do + if is_newer_than "$version" "$line"; then + version="$line" + fi + done < <(jq -r -s '.[0].tags | .[]' <<<"$http_response") + + vecho 2 "Page done, looking for next page" + next_link=$(rel_next <<<"$http_response") + if [[ "$next_link" == "" ]]; then + break; + else + vecho 2 "Next page located at $next_link" + fi + done + + vecho 1 "Latest version of $IMAGE is $version" + echo "$version" +} + +function images_in_file { + # -r is specifically omitted here to allow backslash to escape newlines + while IFS=" " read ln cmd rest; do + if [[ "$cmd" != "FROM" ]]; then + continue + fi + vecho 3 "The args to FROM is $rest" + image=$(cut -d " " -f 1 <<<"$rest") + vecho 1 "Image $image extracted" + # the docker FROM command takes an optional argument that starts with + # --, we don't support that so try and detect it to bail + if [[ "$image" == "-*" ]]; then + printf "Options to FROM are not supported\n" >&2 + exit 1 + fi + echo "$ln:$image" + # We wan't to track the line number to hopefully constrain the coming + # update to the correct part of the dockerfile. Ideally we'd track the + # ftell to be completely accurate, but that's difficult (if not impossible) + # in bash + # The sed removes comment, it skips the first 5 chars to skip the line + # number (4 numbers for the line and one space as a separator) + done < <(nl -w4 -s" " "$1" | sed '/^.{5}\s*#/d' -) +} + +function images_in_kube { + # -r is specifically omitted here to allow backslash to escape newlines + while IFS=" " read ln content; do + regex="^(-\s)?image:\s+([a-zA-Z0-9:/.@]+)\s*$" + if ! [[ $content =~ $regex ]]; then + continue + fi + + image="${BASH_REMATCH[2]}" + echo "$ln:$image" + # We wan't to track the line number to hopefully constrain the coming + # update to the correct part of the file. Ideally we'd track the ftell to + # be completely accurate, but that's difficult (if not impossible) in bash + # The sed removes comment, it skips the first 5 chars to skip the line + # number (4 numbers for the line and one space as a separator) + done < <(nl -w4 -s" " "$1" | sed '/^.{5}\s*#/d' -) +} + +function search_for_newer_version { + while IFS= read -r line; do + ln=$(cut -d":" -f 1 <<<"$line") + content=$(cut -d":" -f 2- <<<"$line") + image_name=$(cut -s -d "@" -f 1 <<<"$content") + digest=$(cut -s -d "@" -f 2 <<<"$content") + tag=$(cut -s -d ":" -f 2 <<<"$image_name") + host=$(cut -s -d "/" -f 1 <<<"$image_name") + image=$(cut -d ":" -f 1 <<<"$image_name" | cut -d "/" -f 2) + + new_tag=$(find_latest_version_of "$host" "$image") + if [[ "$tag" == "$new_tag" ]]; then + continue + fi + + vecho 1 "Image $image on line $ln was $tag should be updated to $new_tag" + echo "$ln:$host:$image:$tag:$new_tag" + done +} + +function generate_script { + # Format the colon separated values as a sed script to be run later (or + # printed if dry-run is on) + awk -F":" '{print $1 "s/" $2 "\\/" $3 ":" $4 "/" $2 "\\/" $3 ":" $5 "/"}' +} + +if [[ $DRY_RUN == "false" ]]; then + function apply_script { + sed -f- -i "$1" + } +else + function apply_script { + script=$(cat) + printf "Would have applied sed script:\n" + printf "%s\n" "$script" + } +fi + +# Digests are excluded since don't really use them +images_in_kube "$FILE" | search_for_newer_version #| generate_script | apply_script "$FILE" -- cgit v1.2.3