|
| 1 | +#!/usr/bin/env bash |
| 2 | +# check-image-pins.sh -- verify container image references are pinned |
| 3 | +# |
| 4 | +# Scans compose files AND shell code under lib/ and scripts/ for container |
| 5 | +# image references that use :latest or no tag at all. The compose-only |
| 6 | +# sweep missed four unpinned alpine references in shell code (issue #1726); |
| 7 | +# this check closes that gap (issue #1728). |
| 8 | +# |
| 9 | +# Rules: |
| 10 | +# 1. compose image: lines must carry a version tag (no :latest, no bare name) |
| 11 | +# 2. no :latest anywhere in *.sh / *.yml under lib/, scripts/, services/ |
| 12 | +# Exempt: localhost/ images (built locally from source, never pulled) |
| 13 | +# and lines carrying an explicit "pin-waiver:" comment with a reason |
| 14 | +# (e.g. ollama MODEL tags, which are not container images) |
| 15 | +# 3. FROM lines in heredoc Dockerfile templates must carry a non-latest tag |
| 16 | +# 4. registry-prefixed references (docker.io, ghcr.io, quay.io) in shell |
| 17 | +# code must carry a tag |
| 18 | + |
| 19 | +set -euo pipefail |
| 20 | + |
| 21 | +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" |
| 22 | +cd "$REPO_ROOT" |
| 23 | + |
| 24 | +SELF="scripts/checks/check-image-pins.sh" |
| 25 | +fail=0 |
| 26 | + |
| 27 | +report() { |
| 28 | + echo "UNPINNED ($1): $2" |
| 29 | + fail=1 |
| 30 | +} |
| 31 | + |
| 32 | +# 1. Compose files: image: lines with :latest or no tag at all |
| 33 | +while IFS= read -r match; do |
| 34 | + report "compose" "$match" |
| 35 | +done < <(grep -Hn "image:" services/docker-compose*.yml 2>/dev/null \ |
| 36 | + | grep -v "#" \ |
| 37 | + | grep -E "image:[[:space:]]+(\S+:latest[[:space:]]*$|\S*/[^:]+[[:space:]]*$|[^/:]+[[:space:]]*$)" || true) |
| 38 | + |
| 39 | +# 2. :latest anywhere in shell or yaml under lib/, scripts/, services/ |
| 40 | +while IFS= read -r match; do |
| 41 | + report ":latest" "$match" |
| 42 | +done < <(grep -rn ":latest" lib/ scripts/ services/ \ |
| 43 | + --include="*.sh" --include="*.yml" 2>/dev/null \ |
| 44 | + | grep -v "^${SELF}:" \ |
| 45 | + | grep -v "localhost/" \ |
| 46 | + | grep -v "pin-waiver:" || true) |
| 47 | + |
| 48 | +# 3. FROM lines in heredoc Dockerfile templates without a version tag |
| 49 | +while IFS= read -r match; do |
| 50 | + report "FROM" "$match" |
| 51 | +done < <(grep -rn "^FROM " lib/ scripts/ --include="*.sh" 2>/dev/null \ |
| 52 | + | grep -vE "^[^:]+:[0-9]+:FROM [^ ]+:[A-Za-z0-9][A-Za-z0-9._-]*([[:space:]]|$)" \ |
| 53 | + | grep -v "^${SELF}:" || true) |
| 54 | + |
| 55 | +# 4. Registry-prefixed references in shell code without a tag |
| 56 | +while IFS= read -r match; do |
| 57 | + report "no tag" "$match" |
| 58 | +done < <(grep -rnE "(docker\.io|ghcr\.io|quay\.io)/[A-Za-z0-9._/-]+" \ |
| 59 | + lib/ scripts/ --include="*.sh" 2>/dev/null \ |
| 60 | + | grep -vE "(docker\.io|ghcr\.io|quay\.io)/[A-Za-z0-9._/-]+:[A-Za-z0-9]" \ |
| 61 | + | grep -v "^${SELF}:" || true) |
| 62 | + |
| 63 | +if [ "$fail" -eq 1 ]; then |
| 64 | + echo "FAIL: unpinned container image references found" |
| 65 | + exit 1 |
| 66 | +fi |
| 67 | + |
| 68 | +echo "All container image references are pinned" |
| 69 | +exit 0 |
0 commit comments