#!/bin/bash # Generate Image Digest Lock File # Collects currently running images and resolves immutable digests per host set -euo pipefail usage() { cat << EOF Generate Image Digest Lock File Usage: $0 --hosts "omv800 surface fedora" --output /opt/migration/configs/image-digest-lock.yaml Options: --hosts Space-separated hostnames to query over SSH (required) --output Output lock file path (default: ./image-digest-lock.yaml) --help Show this help Notes: - Requires passwordless SSH or ssh-agent for each host - Each host must have Docker CLI and network access to resolve digests - Falls back to remote `docker image inspect` to fetch RepoDigests EOF } HOSTS="" OUTPUT="./image-digest-lock.yaml" while [[ $# -gt 0 ]]; do case "$1" in --hosts) HOSTS="$2"; shift 2 ;; --output) OUTPUT="$2"; shift 2 ;; --help|-h) usage; exit 0 ;; *) echo "Unknown argument: $1" >&2; usage; exit 1 ;; esac done if [[ -z "$HOSTS" ]]; then echo "--hosts is required" >&2 usage exit 1 fi TMP_DIR=$(mktemp -d) trap 'rm -rf "$TMP_DIR"' EXIT echo "# Image Digest Lock" > "$OUTPUT" echo "# Generated: $(date -Iseconds)" >> "$OUTPUT" echo "hosts:" >> "$OUTPUT" for HOST in $HOSTS; do echo " $HOST:" >> "$OUTPUT" # Get running images (name:tag or id) IMAGES=$(ssh -o ConnectTimeout=10 "$HOST" "docker ps --format '{{.Image}}'" 2>/dev/null || true) if [[ -z "$IMAGES" ]]; then echo " images: []" >> "$OUTPUT" continue fi echo " images:" >> "$OUTPUT" while IFS= read -r IMG; do [[ -z "$IMG" ]] && continue # Inspect to get RepoDigests (immutable digests) INSPECT_JSON=$(ssh "$HOST" "docker image inspect '$IMG'" 2>/dev/null || true) if [[ -z "$INSPECT_JSON" ]]; then # Try to pull metadata silently to populate digest cache (without actual layer download) ssh "$HOST" "docker pull --quiet '$IMG' > /dev/null 2>&1 || true" INSPECT_JSON=$(ssh "$HOST" "docker image inspect '$IMG'" 2>/dev/null || true) fi DIGEST_LINE="" if command -v jq >/dev/null 2>&1; then DIGEST_LINE=$(echo "$INSPECT_JSON" | jq -r '.[0].RepoDigests[0] // ""' 2>/dev/null || echo "") else # Grep/sed fallback: find first RepoDigests entry DIGEST_LINE=$(echo "$INSPECT_JSON" | grep -m1 'RepoDigests' -A2 | grep -m1 sha256 | sed 's/[", ]//g' || true) fi # If no digest, record unresolved entry if [[ -z "$DIGEST_LINE" || "$DIGEST_LINE" == "null" ]]; then echo " - image: \"$IMG\"" >> "$OUTPUT" echo " resolved: false" >> "$OUTPUT" continue fi # Split repo@sha digest IMAGE_AT_DIGEST="$DIGEST_LINE" # Try to capture the original tag (if present) ORIG_TAG="$IMG" echo " - image: \"$ORIG_TAG\"" >> "$OUTPUT" echo " digest: \"$IMAGE_AT_DIGEST\"" >> "$OUTPUT" echo " resolved: true" >> "$OUTPUT" done <<< "$IMAGES" done echo "\nWrote lock file: $OUTPUT"