Skip to content

Commit b35e478

Browse files
authored
Merge pull request xencon#1729 from sbadakhc/issue-1728/extend-image-pin-check
Extend image pin check to lib and scripts (xencon#1728)
2 parents 79c3aaf + 8680b02 commit b35e478

8 files changed

Lines changed: 100 additions & 16 deletions

File tree

.claude/rules/ci-checks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,6 @@ gh pr create \
6969
shellcheck --severity=warning --exclude=SC1091 $(find . -name "*.sh" -not -path "./.git/*")
7070

7171
# Individual checks
72-
./aixcl checks paths # or: agents, elisions, generated, ascii, yaml, compose, env
72+
./aixcl checks paths # or: agents, elisions, generated, ascii, pins, yaml, compose, env
7373
./aixcl checks pr-refs <body-file>
7474
```

.claude/skills/housekeeping/SKILL.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,19 @@ fi
165165
- [ ] If gitleaks not installed, note it as a tooling gap
166166
- [ ] Note: `--no-git` scans ALL files on disk, including gitignored runtime files (e.g. `pgadmin-servers.json`). Findings in gitignored runtime files belong in the `.gitleaks.toml` `paths` allowlist, not the `commits` allowlist.
167167

168-
### Step 8 -- Docker Image Pin Hygiene
168+
### Step 8 -- Container Image Pin Hygiene
169+
170+
Covers compose files AND shell code under `lib/` and `scripts/` -- four
171+
unpinned alpine references hid in shell code because the old sweep only
172+
scanned compose (issue #1726). Legitimate `:latest` uses (locally built
173+
`localhost/` images, ollama model tags) are exempted via `localhost/`
174+
detection or an inline `pin-waiver:` comment.
169175

170176
```bash
171-
grep -hn "image:" services/docker-compose*.yml | grep -v "#" | \
172-
grep -E "image:\s+(\S+:latest\s*$|\S*/[^:]+\s*$|[^/:]+\s*$)" \
173-
&& echo "FAIL: unpinned image tags found above" || echo "ok: all images pinned"
177+
./aixcl checks pins
174178
```
175179

176-
- [ ] All images in all compose files use pinned version tags (no `latest`, no bare image names)
180+
- [ ] All container image references are pinned (no `latest`, no bare image names), or carry an explicit `pin-waiver:` comment with a reason
177181

178182
### Step 9 -- Shellcheck Sweep (All Scripts)
179183

.opencode/rules/ci-checks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,6 @@ gh pr create \
6969
shellcheck --severity=warning --exclude=SC1091 $(find . -name "*.sh" -not -path "./.git/*")
7070

7171
# Individual checks
72-
./aixcl checks paths # or: agents, elisions, generated, ascii, yaml, compose, env
72+
./aixcl checks paths # or: agents, elisions, generated, ascii, pins, yaml, compose, env
7373
./aixcl checks pr-refs <body-file>
7474
```

.opencode/skills/housekeeping/SKILL.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,19 @@ fi
165165
- [ ] If gitleaks not installed, note it as a tooling gap
166166
- [ ] Note: `--no-git` scans ALL files on disk, including gitignored runtime files (e.g. `pgadmin-servers.json`). Findings in gitignored runtime files belong in the `.gitleaks.toml` `paths` allowlist, not the `commits` allowlist.
167167

168-
### Step 8 -- Docker Image Pin Hygiene
168+
### Step 8 -- Container Image Pin Hygiene
169+
170+
Covers compose files AND shell code under `lib/` and `scripts/` -- four
171+
unpinned alpine references hid in shell code because the old sweep only
172+
scanned compose (issue #1726). Legitimate `:latest` uses (locally built
173+
`localhost/` images, ollama model tags) are exempted via `localhost/`
174+
detection or an inline `pin-waiver:` comment.
169175

170176
```bash
171-
grep -hn "image:" services/docker-compose*.yml | grep -v "#" | \
172-
grep -E "image:\s+(\S+:latest\s*$|\S*/[^:]+\s*$|[^/:]+\s*$)" \
173-
&& echo "FAIL: unpinned image tags found above" || echo "ok: all images pinned"
177+
./aixcl checks pins
174178
```
175179

176-
- [ ] All images in all compose files use pinned version tags (no `latest`, no bare image names)
180+
- [ ] All container image references are pinned (no `latest`, no bare image names), or carry an explicit `pin-waiver:` comment with a reason
177181

178182
### Step 9 -- Shellcheck Sweep (All Scripts)
179183

completion/aixcl.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ _aixcl_complete() {
198198
return 0
199199
;;
200200
'checks')
201-
local checks_actions="all paths agents elisions generated ascii yaml compose env pr-refs"
201+
local checks_actions="all paths agents elisions generated ascii pins yaml compose env pr-refs"
202202
mapfile -t COMPREPLY < <(compgen -W "$checks_actions" -- "$cur")
203203
return 0
204204
;;

lib/aixcl/commands/checks.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ CHECKS_FAILED=()
99
CHECKS_SKIPPED=()
1010

1111
_checks_usage() {
12-
echo "Usage: $0 checks {all|paths|agents|elisions|generated|ascii|yaml|compose|env|pr-refs <file>}"
12+
echo "Usage: $0 checks {all|paths|agents|elisions|generated|ascii|pins|yaml|compose|env|pr-refs <file>}"
1313
echo " all Run every check below (continues on failure, prints summary)"
1414
echo " paths Documentation relative links and stale path patterns"
1515
echo " agents .claude/ vs .opencode/ rules and skills mirror parity"
1616
echo " elisions AI-elision placeholders and suspicious mass deletions"
1717
echo " generated Tracked generated files and stale artifacts"
18+
echo " pins Container image references pinned (compose + shell code)"
1819
echo " ascii Non-ASCII punctuation in markdown files"
1920
echo " yaml yamllint over the repository"
2021
echo " compose docker compose config validation (main + overrides)"
@@ -140,6 +141,9 @@ function checks_cmd() {
140141
elisions)
141142
bash "${checks_dir}/check-ai-elisions.sh" "$@"
142143
;;
144+
pins)
145+
bash "${checks_dir}/check-image-pins.sh"
146+
;;
143147
generated)
144148
bash "${checks_dir}/check-generated-files.sh"
145149
;;
@@ -177,6 +181,7 @@ function checks_cmd() {
177181
_check_run "elisions" bash "${checks_dir}/check-ai-elisions.sh" || true
178182
_check_run "generated" bash "${checks_dir}/check-generated-files.sh" || true
179183
_check_run "ascii" _check_ascii || true
184+
_check_run "pins" bash "${checks_dir}/check-image-pins.sh" || true
180185

181186
if command -v yamllint > /dev/null 2>&1; then
182187
_check_run "yaml" yamllint -c "${SCRIPT_DIR}/.yamllint.yml" "${SCRIPT_DIR}" || true

lib/aixcl/commands/models.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ is_model_installed() {
99

1010
case "$engine" in
1111
ollama)
12-
# Ollama list shows models. We need to match the name exactly, or with :latest if missing tag
12+
# Ollama list shows models. Match the name exactly, or with the
13+
# default tag appended when the name carries none.
14+
# (pin-waiver: ollama MODEL tags are not container images)
1315
local model_with_tag="$model"
14-
[[ ! "$model" =~ : ]] && model_with_tag="${model}:latest"
16+
[[ ! "$model" =~ : ]] && model_with_tag="${model}:latest" # pin-waiver: ollama model tag
1517
"${DOCKER_BIN:-docker}" exec "$container" ollama list 2>/dev/null | awk '{print $1}' | grep -qE "^${model_with_tag}$"
1618
return $?
1719
;;

scripts/checks/check-image-pins.sh

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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

Comments
 (0)