276 lines
8.7 KiB
Bash
Executable File
276 lines
8.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -o errexit
|
|
set -o nounset
|
|
set -o pipefail
|
|
|
|
# Output more information that is out of sync
|
|
DEBUG="${DEBUG:-}"
|
|
|
|
IMAGE1="${1:-}"
|
|
IMAGE2="${2:-}"
|
|
|
|
SKOPEO="${SKOPEO:-skopeo}"
|
|
JQ="${JQ:-jq}"
|
|
|
|
# Allow image2 to have more tags than image1
|
|
INCREMENTAL="${INCREMENTAL:-}"
|
|
|
|
# Compare only tags that are in both images
|
|
QUICKLY="${QUICKLY:-}"
|
|
|
|
# Regexp that matches the tags
|
|
FOCUS="${FOCUS:-}"
|
|
|
|
# Regexp that matches the tags that needs to be skipped
|
|
SKIP="${SKIP:-}"
|
|
|
|
# Compare the number of tags in parallel
|
|
PARALLET="${PARALLET:-0}"
|
|
|
|
SELF="$(basename "${BASH_SOURCE[0]}")"
|
|
|
|
if [[ "${DEBUG}" == "true" ]]; then
|
|
echo "DEBUG: ${DEBUG}"
|
|
echo "IMAGE1: ${IMAGE1}"
|
|
echo "IMAGE2: ${IMAGE2}"
|
|
echo "SKOPEO: ${SKOPEO}"
|
|
echo "JQ: ${JQ}"
|
|
echo "INCREMENTAL: ${INCREMENTAL}"
|
|
echo "QUICKLY: ${QUICKLY}"
|
|
echo "FOCUS: ${FOCUS}"
|
|
echo "SKIP: ${SKIP}"
|
|
echo "PARALLET: ${PARALLET}"
|
|
fi
|
|
|
|
function check() {
|
|
local image1="${1:-}"
|
|
local image2="${2:-}"
|
|
|
|
if [[ "${image1}" == "" ]] || [[ "${image2}" == "" ]]; then
|
|
echo "Compares whether the synchronization of the two images is exactly the same"
|
|
echo "Need to install jq and skopeo"
|
|
echo "Usage:"
|
|
echo " ${SELF}: <image1> <image2>"
|
|
echo " ${SELF}: <image1:tag> <image2:tag>"
|
|
echo "Env:"
|
|
echo " DEBUG=true # Output more information that is out of sync"
|
|
echo " INCREMENTAL=true # Allow image2 to have more tags than image1"
|
|
echo " QUICKLY=true # Compare only tags that are in both images"
|
|
echo " FOCUS=<pattern> # Regexp that matches the tags"
|
|
echo " SKIP=<pattern> # Regexp that matches the tags that needs to be skipped"
|
|
echo " PARALLET=<size> # Compare the number of tags in parallel"
|
|
return 2
|
|
fi
|
|
|
|
if [[ "${image1}" =~ ":" ]]; then
|
|
if [[ "${image2}" =~ ":" ]]; then
|
|
return 0
|
|
else
|
|
echo "${SELF}: ERROR: ${image1} and ${image2} must both be full images or not be tag references" >&2
|
|
return 2
|
|
fi
|
|
else
|
|
if [[ "${image2}" =~ ":" ]]; then
|
|
echo "${SELF}: ERROR: ${image1} and ${image2} must both be full images or not be tag references" >&2
|
|
return 2
|
|
else
|
|
return 0
|
|
fi
|
|
fi
|
|
}
|
|
|
|
function inspect() {
|
|
local image="${1:-}"
|
|
local raw=$(${SKOPEO} inspect --raw --tls-verify=false "docker://${image}")
|
|
if [[ "${raw}" == "" ]]; then
|
|
echo "skopeo inspect --raw --tls-verify=false docker://${image}" >&2
|
|
echo "ERROR: Failed to inspect ${image}" >&2
|
|
return 1
|
|
fi
|
|
|
|
local schemaVersion=$(echo "${raw}" | ${JQ} -r '.schemaVersion')
|
|
case "${schemaVersion}" in
|
|
1)
|
|
echo "${raw}" | ${JQ} -r '.fsLayers[].blobSum'
|
|
;;
|
|
2)
|
|
local mediaType=$(echo "${raw}" | ${JQ} -r '.mediaType // "" ')
|
|
if [[ "${mediaType}" == "" ]]; then
|
|
if [[ "$(echo "${raw}" | ${JQ} -r '.layers | length')" -gt 0 ]]; then
|
|
mediaType="layers"
|
|
elif [[ "$(echo "${raw}" | ${JQ} -r '.manifests | length')" -gt 0 ]]; then
|
|
mediaType="manifests"
|
|
fi
|
|
fi
|
|
|
|
case "${mediaType}" in
|
|
"layers" | "application/vnd.docker.distribution.manifest.v2+json")
|
|
echo "${raw}" | ${JQ} -r '.layers[].digest'
|
|
;;
|
|
"manifests" | "application/vnd.docker.distribution.manifest.list.v2+json")
|
|
echo "${raw}" | ${JQ} -j '.manifests[] | .platform.architecture , " " , .platform.os , " " , .digest , "\n"' | sort
|
|
;;
|
|
*)
|
|
echo "skopeo inspect --raw --tls-verify=false docker://${image}" >&2
|
|
if [[ "${DEBUG}" == "true" ]]; then
|
|
echo "${raw}" >&2
|
|
fi
|
|
echo "${SELF}: ERROR: Unknown media type: ${mediaType}" >&2
|
|
return 2
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
echo "skopeo inspect --raw --tls-verify=false docker://${image}" >&2
|
|
if [[ "${DEBUG}" == "true" ]]; then
|
|
echo "${raw}" >&2
|
|
fi
|
|
echo "${SELF}: ERROR: Unknown schema version: ${schemaVersion}" >&2
|
|
return 2
|
|
;;
|
|
esac
|
|
}
|
|
|
|
function list-tags() {
|
|
local image="${1:-}"
|
|
local raw="$(${SKOPEO} list-tags --tls-verify=false "docker://${image}" | ${JQ} -r '.Tags[]' | sort)"
|
|
|
|
if [[ "${FOCUS}" != "" ]]; then
|
|
raw="$(echo "${raw}" | grep -E "${FOCUS}" || :)"
|
|
fi
|
|
|
|
if [[ "${SKIP}" != "" ]]; then
|
|
raw="$(echo "${raw}" | grep -v -E "${SKIP}" || :)"
|
|
fi
|
|
echo "${raw}"
|
|
}
|
|
|
|
function diff-image-with-tag() {
|
|
local image1="${1:-}"
|
|
local image2="${2:-}"
|
|
|
|
if [[ "${QUICKLY}" == "true" ]]; then
|
|
local tag1="${image1##*:}"
|
|
local tag2="${image2##*:}"
|
|
if [[ "${tag1}" != "${tag2}" ]]; then
|
|
echo "${SELF}: UNSYNC: ${image1} and ${image2} are not in synchronized" >&2
|
|
return 1
|
|
fi
|
|
echo "${SELF}: SYNC: ${image1} and ${image2} are in synchronized" >&2
|
|
return 0
|
|
fi
|
|
|
|
local inspect2="$(inspect ${image2})"
|
|
if [[ "${inspect2}" == "" ]]; then
|
|
echo "${SELF}: UNSYNC: ${image1} and ${image2} are not in synchronized, ${image2} content is empty" >&2
|
|
return 1
|
|
fi
|
|
|
|
local inspect1="$(inspect ${image1})"
|
|
local diff_raw=$(diff --unified <(echo "${inspect1}") <(echo "${inspect2}"))
|
|
|
|
if [[ "${diff_raw}" != "" ]]; then
|
|
echo "${SELF}: UNSYNC: ${image1} and ${image2} are not in synchronized" >&2
|
|
if [[ "${DEBUG}" == "true" ]]; then
|
|
echo "DEBUG: image1 ${image1}:" >&2
|
|
echo "${inspect1}" >&2
|
|
echo "DEBUG: image2 ${image2}:" >&2
|
|
echo "${inspect2}" >&2
|
|
echo "diff:" >&2
|
|
echo "${diff_raw}" >&2
|
|
fi
|
|
return 1
|
|
fi
|
|
echo "${SELF}: SYNC: ${image1} and ${image2} are in synchronized" >&2
|
|
}
|
|
|
|
function diff-image() {
|
|
local image1="${1:-}"
|
|
local image2="${2:-}"
|
|
|
|
local tags1="$(list-tags ${image1})"
|
|
local tags2="$(list-tags ${image2})"
|
|
local diff_raw="$(diff --unified <(echo "${tags1}") <(echo "${tags2}") | grep -v -E '^---' | grep -v -E '^\+\+\+' || :)"
|
|
local increase="$(echo "${diff_raw}" | grep -E '^\+' | sed 's/^\+//' || :)"
|
|
local reduce="$(echo "${diff_raw}" | grep -E '^-' | sed 's/^-//' || :)"
|
|
local common="${tags1}"
|
|
|
|
if [[ "${increase}" != "" ]]; then
|
|
common="$(echo "${common}" | grep -v -f <(echo "${increase}") || :)"
|
|
fi
|
|
|
|
if [[ "${reduce}" != "" ]]; then
|
|
common="$(echo "${common}" | grep -v -f <(echo "${reduce}") || :)"
|
|
fi
|
|
|
|
if [[ "${INCREMENTAL}" == "true" ]]; then
|
|
increase=""
|
|
fi
|
|
|
|
if [[ "${reduce}" != "" ]] || [[ "${increase}" != "" ]]; then
|
|
echo "${SELF}: UNSYNC-TAGS: ${image1} and ${image2} are not in synchronized" >&2
|
|
if [[ "${DEBUG}" == "true" ]]; then
|
|
echo "DEBUG: image1 ${image1}:" >&2
|
|
echo "${tags1}" >&2
|
|
echo "DEBUG: image2 ${image2}:" >&2
|
|
echo "${tags2}" >&2
|
|
echo "DEBUG: diff:" >&2
|
|
echo "${diff_raw}" >&2
|
|
fi
|
|
for tag in ${reduce}; do
|
|
echo "${SELF}: UNSYNC: ${image1}:${tag} and ${image2}:${tag} are not in synchronized, ${image2}:${tag} does not exist" >&2
|
|
done
|
|
for tag in ${increase}; do
|
|
echo "${SELF}: UNSYNC: ${image1}:${tag} and ${image2}:${tag} are not in synchronized, ${image1}:${tag} does not exist" >&2
|
|
done
|
|
echo "${common}"
|
|
return 1
|
|
fi
|
|
echo "${SELF}: SYNC-TAGS: ${image1} and ${image2} are in synchronized" >&2
|
|
echo "${common}"
|
|
return 0
|
|
}
|
|
|
|
function wait_jobs() {
|
|
local job_num=${1:-3}
|
|
local perc=$(jobs -p | wc -l)
|
|
while [ "${perc}" -gt "${job_num}" ]; do
|
|
sleep 1
|
|
perc=$(jobs -p | wc -l)
|
|
done
|
|
}
|
|
|
|
function main() {
|
|
local image1="${1:-}"
|
|
local image2="${2:-}"
|
|
|
|
if [[ "${image1}" =~ ":" ]]; then
|
|
diff-image-with-tag "${image1}" "${image2}" >/dev/null || return $?
|
|
return 0
|
|
fi
|
|
|
|
local list=$(diff-image "${image1}" "${image2}")
|
|
|
|
local unsync=()
|
|
if [[ "${QUICKLY}" == "true" ]] || [[ "${PARALLET}" -eq 0 ]]; then
|
|
for tag in ${list}; do
|
|
diff-image-with-tag "${image1}:${tag}" "${image2}:${tag}" >/dev/null || unsync+=("${tag}")
|
|
done
|
|
else
|
|
for tag in ${list}; do
|
|
wait_jobs "${PARALLET}"
|
|
diff-image-with-tag "${image1}:${tag}" "${image2}:${tag}" >/dev/null || unsync+=("${tag}") &
|
|
done
|
|
wait
|
|
fi
|
|
|
|
if [[ "${#unsync[@]}" -gt 0 ]]; then
|
|
echo "${SELF}: INFO: ${image1} and ${image2} are not in synchronized, there are unsynchronized tags ${#unsync[@]}: ${unsync[*]}" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
check "${IMAGE1}" "${IMAGE2}"
|
|
main "${IMAGE1}" "${IMAGE2}"
|