Skip to content

fix: remove_unused_images empty SSH output on remote#8

Merged
fichte merged 1 commit into
mainfrom
fix-remove-unused-images
Apr 27, 2026
Merged

fix: remove_unused_images empty SSH output on remote#8
fichte merged 1 commit into
mainfrom
fix-remove-unused-images

Conversation

@fichte

@fichte fichte commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Symptom

Reported on a real prod deploy of prodbc:

+ OUTPUT_COMPOSE=

with the user noting the same ssh ... '{ docker compose --env-file …/.env images | sed 1d; }' produces 7 image rows when run by hand. The script captures empty stdout for the same command.

Root cause

This file was the only functions/_*.sh that PR #6 didn't touch. It still had every pattern that PR #6 fixed elsewhere:

  • 8 hand-rolled local-vs-remote dual-branches concatenating '…'\"\${var}\"'…'-style SSH payloads.

  • for u in \seq 0 N-1`loops broken on BSDseq` (counts down through -1 instead of producing nothing).

  • This sed pipeline at line 89:

    'docker images | grep "'"${CI_REGISTRY}"'/'"${CI_PROJECT_PATH}"'/" | sed '/"${OUTPUT_CLI_KEEP_VERSION}"/d''

    Count the quotes. The /V/d ends up with literal double-quotes around the version on the remote shell — not what sed accepts. Most invocations returned empty.

Fix

Same refactor pattern PR #6 applied to _deploy.sh:

  • Every dual-branch collapsed onto run_in_target / compose_in_target. Args are shell-quoted with printf %q for the SSH path, so quoting traps go away.
  • for ((i=0; i<${#arr[@]}; i++)) and while IFS= read -r line; no seq.
  • docker image ls --format '{{.Repository}}:{{.Tag}}' directly, so column counting in awk/sed is replaced by precise filters.
  • grep -vF ":\${VERSION}" (fixed-string) replaces the broken sed '/V/d'. Same intent, no quoting trap.
  • set -e survival via || true on every grep that may legitimately match nothing.
  • exit 0return 0 for consistency with the rest of the file set.

Test plan

  • bash -n clean.
  • bats tests/bats/ — 46/46 still green (this function isn't covered by the fixture; the smoke test exercises generate, not unused).
  • make gpd-generate against the xyxyx/cloud parent — passes (no regression on the unrelated paths).
  • gpd.sh -e prodbc -b /srv/docker -u against a real remote host with images to clean — needs you to verify, since the bug only fires on remote.

…ng bugs

Reported on a real prod deploy:

  + OUTPUT_COMPOSE=

with a side-by-side showing the same ssh command produces 7 image rows
when run by hand. The stdout vanished when the gpd script ran the
identical command — symptom of bash word-splitting and shell quoting
bugs that this file dodged in PR #6.

This file was the only one in functions/ that PR #6 didn't touch. It
still had:

  - dual-branch local-vs-remote (8 sites). Calls `gpd ssh "${user}@${host}" 'cmd'`
    with hand-rolled string concatenation, which is exactly what the new
    helpers exist to replace.
  - `for u in \`seq 0 N-1\`` loops, broken on macOS BSD seq the same
    way _config_files.sh was.
  - This particular gem at line 89:

      'docker images | grep "'"${CI_REGISTRY}"'/'"${CI_PROJECT_PATH}"'/" | sed '/"${OUTPUT_CLI_KEEP_VERSION}"/d''

    Count the quotes. The `'/"${VERSION}"/d'` segment escapes to the
    remote shell as `/V/d` -- with literal double-quotes around the
    version, which is _not_ what `sed /pat/d` accepts. Whatever this
    pipeline actually executed on the remote was at best a fluke; on
    most setups it returned an empty string.

Refactor:

  - Every dual-branch collapsed onto run_in_target / compose_in_target.
    Same helpers _deploy.sh switched to in PR #6, with printf %q quoting
    of every argument before they reach the SSH payload.
  - `for ((i=0; i<${#arr[@]}; i++))` and `while read` instead of
    seq-based index loops; works on BSD seq, GNU seq, no seq.
  - `docker image ls --format '{{.Repository}}:{{.Tag}}'` directly,
    so we never have to count columns or parse sed/awk pipelines.
  - `grep -vF ":${VERSION}"` (fixed-string, anchored to the tag suffix)
    in place of the broken `sed '/V/d'`. Same intent, no quoting trap.
  - `set -e` survival via `|| true` on every grep that may legitimately
    match nothing (no compose-managed images, no old custom builds).
  - `exit 0` -> `return 0` for consistency with the rest of the file
    set, and so the caller's control flow stays clean.

CHANGELOG: noted under [Unreleased] -> Fixed.

bats locally: 46/46 still green (this function isn't covered by the
fixture; the smoke test exercises generate, not unused). End-to-end
verification needs an actual remote host with images to clean — same
gap the user just hit.
@fichte fichte merged commit 124cbe3 into main Apr 27, 2026
3 checks passed
@fichte fichte deleted the fix-remove-unused-images branch April 27, 2026 14:23
@fichte fichte mentioned this pull request Apr 27, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant