#!/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"