Skip to content

Instantly share code, notes, and snippets.

@undying
Created April 25, 2025 20:36
Show Gist options
  • Save undying/531396dc3e2987dac78ec9fcb987dec1 to your computer and use it in GitHub Desktop.
Save undying/531396dc3e2987dac78ec9fcb987dec1 to your computer and use it in GitHub Desktop.
Script to bump image versions in docker-compose.yml file
#! /bin/bash
VERSION_TYPES=(
# 29
'^[0-9]{1,}$'
# 29-fpm
'^[0-9]{1,}-[a-z]{1,}$'
# 2024040105
'^[0-9]{10}$'
# 4.0.10-develop
'^[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}-[a-z]{1,}$'
# 1.11.4.4173
'^[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}(\.[0-9]{1,})?$'
# 1.11.4.4173-ls45
'^[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}-[0-9a-z]{1,}$'
# v0.21.1025
'^v[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}$'
# v0.21.1025-ls191
'^v[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}-[0-9a-z]{1,}$'
)
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
SED_CMD="sed -i -f"
INPUT_FILE=docker-compose.yml
VERBOSE=0
DRY_RUN=0
declare -A IMAGES_EXCLUDE
declare -A IMAGES_ONLY
declare -A VERSION_BLACKLIST
case "${OS}" in
darwin)
SED_CMD="sed -i '' -f"
;;
esac
function compare_suffix(){
local version_a=${1} version_b=${2}
local suffix_a=${version_a##*-} suffix_b=${version_b##*-}
[[ "${suffix_a}" == "${suffix_b}" ]] && return 0
return 1
}
export -f compare_suffix
function blacklist_file_parse(){
local input_file="${1}"
if [[ ! -f "${input_file}" ]];then
printf 'No such file: %s\n' "${input_file}" >&2
return 1
fi
tr '\n' ' ' < "${input_file}"
}
export -f blacklist_file_parse
function clean_version(){
local version=${1}
version=${version#v}
version=${version%-*}
printf '%s' "${version}"
}
export -f clean_version
function compare_versions_semver(){
local i
local IFS=.
local version_a=(${1//-/.}) version_b=(${2//-/.})
while [[ ${#version_a[@]} -gt ${#version_b[@]} ]];do version_b+=('0');done
while [[ ${#version_a[@]} -lt ${#version_b[@]} ]];do version_a+=('0');done
for ((i=0; i<${#version_a[@]}; i++));do
if [[ ${version_a[i]} =~ ^[0-9]+$ && ${version_b[i]} =~ ^[0-9]+$ ]];then
((10#${version_a[i]} > 10#${version_b[i]})) && return 1
((10#${version_a[i]} < 10#${version_b[i]})) && return 2
else
[[ ${version_a[i]} > ${version_b[i]} ]] && return 1
[[ ${version_a[i]} < ${version_b[i]} ]] && return 2
fi
done
return 0
}
export -f compare_versions_semver
function compare_versions(){
local version_a=${1} version_b=${2}
get_version_type "${version_a}"
local version_type=${?}
case ${version_type} in
0)
[[ ${version_a} -gt ${version_b} ]] && return 1
[[ ${version_a} -lt ${version_b} ]] && return 2
return 0
;;
[1-2])
[[ "${version_a}" > "${version_b}" ]] && return 1
[[ "${version_a}" < "${version_b}" ]] && return 2
return 0
;;
[3-7])
compare_versions_semver "${version_a}" "${version_b}"
return ${?}
;;
255)
printf "Unknown version type: %s\n" "${version_a}"
return 255
;;
*)
printf "Unknown version code: %d, %s\n" "${version_type}" "${version_a}"
return 255
;;
esac
}
export -f compare_versions
function tags_url(){
local image="${1}"
printf 'https://registry.hub.docker.com/v2/repositories/%s/tags?page_size=1024&architecture=amd64' "${image}"
}
export -f tags_url
function get_images(){
local f=${1}
grep 'image:' "${f}" \
|awk '{print $2}'
}
export -f get_images
function get_images_from_registry(){
local url="${1}"
curl -sS "${url}" \
|jq -r '.results[] | select(.images[] | select(.architecture == "amd64")) | .name'
}
export -f get_images_from_registry
function get_image_and_version(){
local image_full="${1}"
echo "${image_full//:/ }"
}
export -f get_image_and_version
function get_version_type(){
local version="${1}"
local count=0
for t in "${VERSION_TYPES[@]}";do
[[ "${version}" =~ ${t} ]] && return ${count}
count=$((count+1))
done
return 255
}
function main(){
local image version image_full
local url
while read -r image_full;do
read -r image version < <(get_image_and_version "${image_full}")
if [[ ${#IMAGES_ONLY[@]} -gt 0 && ${IMAGES_ONLY[${image}]} != 1 ]];then
printf 'Skipping image: %s\n' "${image}" >&2
continue
fi
if [[ ${#IMAGES_EXCLUDE[@]} -gt 0 && ${IMAGES_EXCLUDE[${image}]} == 1 ]];then
printf 'Skipping excluded image: %s\n' "${image}" >&2
continue
fi
url=$(tags_url "${image}")
local last="${version}"
get_version_type "${version}"
local version_type=${?}
if [[ ${version_type} -eq 255 ]];then
printf 'Version type not found for %s\n' "${image_full}" >&2
continue
fi
while read -r registry_version;do
get_version_type "${registry_version}"
local registry_version_type=${?}
[[ ${version_type} -eq ${registry_version_type} ]] || continue
compare_suffix "${version}" "${registry_version}" || continue
local registry_image_version="${image}:${registry_version}"
if [[ ${#VERSION_BLACKLIST[@]} -gt 0 && ${VERSION_BLACKLIST[${registry_image_version}]} == 1 ]];then
printf 'Skipping blacklisted version: %s\n' "${registry_image_version}" >&2
continue
fi
compare_versions "${version}" "${registry_version}"
local compare_result=${?}
[[ ${compare_result} -eq 2 ]] && last="${registry_version}"
[[ ${VERBOSE} -gt 0 ]] && printf '%s | %s\n' "${image_full}" "${registry_version}"
done < <(get_images_from_registry "${url}")
if [[ "${version}" == "${last}" ]];then
printf 'No newer version found for %s\n' "${image_full}" >&2
continue
fi
printf '%s -> %s:%s\n' "${image_full}" "${image}" "${last}"
cat - > "${tmp_file}" <<EOF
s|${image_full}|${image}:${last}|g
EOF
if [[ "${DRY_RUN}" -gt 0 ]];then
echo ${SED_CMD} "${tmp_file}" "${INPUT_FILE}"
else
${SED_CMD} "${tmp_file}" "${INPUT_FILE}"
fi
done < <(get_images ${INPUT_FILE}|sort -u)
}
[[ "${BASH_SOURCE[0]}" == "${0}" ]] || return 1
tmp_file=$(mktemp)
trap "rm -f ${tmp_file}" EXIT
while [[ "${#}" -gt 0 ]];do
case ${1} in
--verbose|-v)
VERBOSE=1
;;
--dry-run|-n)
DRY_RUN=1
;;
--exclude|-E)
[[ -n ${2} ]] || exit
for i in ${2//,/ };do
IMAGES_EXCLUDE[${i}]=1
done
shift
;;
--only|-O)
[[ -n ${2} ]] || exit
for i in ${2//,/ };do
IMAGES_ONLY[${i}]=1
done
shift
;;
--blacklist-file|-B)
[[ -n ${2} ]] || exit
if [[ ! -f "${2}" ]];then
printf 'No such file: %s\n' "${2}" >&2
exit 1;
fi
while read -r version;do
VERSION_BLACKLIST[${version}]=1
done < "${2}"
shift
;;
--help|-h)
printf 'Usage: %s [OPTIONS]\n' "${0}"
printf 'OPTIONS:\n'
printf ' --verbose, -v\tVerbose mode\n'
printf ' --dry-run, -n\tDry run mode\n'
printf ' --blacklist-file, -B\tBlacklist file\n'
exit
;;
*)
printf 'Unknown argument: %s\n' "${1}"
;;
esac
shift
done
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment