Conversation
📝 WalkthroughWalkthroughThis change introduces a version management system for monitoring and updating upstream container image versions. It adds a GitHub Actions workflow that runs on a weekly schedule to check for new versions across configured packages, compare them with local defaults in docker-compose.yaml, and automatically create pull requests with updates. Supporting infrastructure includes three Bash scripts (check-versions.sh, add-version.sh, validate-versions.sh), a JSON configuration file mapping services to their image sources and version patterns, three new Makefile targets, and documentation in the README describing the version maintenance workflow. 🚥 Pre-merge checks | ✅ 1 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
scripts/add-version.sh (1)
80-80: Minor:new_versioninre.subreplacement string is not escaped.If
new_versionwere to contain backslash characters (e.g., in an unusual tag),re.subwould interpret\1,\2, etc. as backreferences. Extremely unlikely for version strings, butre.escapeisn't the right fix for replacement strings — usenew_version.replace('\\', '\\\\')if you want to be defensive.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/add-version.sh` at line 80, The replacement passed to pattern.sub uses new_version directly so backslashes in new_version could be interpreted as regex backreferences; update the call that assigns updated (the pattern.sub(..., new_version ...)) to escape backslashes in new_version before passing it to pattern.sub (e.g., replace every '\' with '\\') so the replacement string is treated literally; locate the pattern.sub usage that assigns variable updated and apply new_version.replace('\\','\\\\') (or equivalent) to the replacement argument..github/workflows/check-versions.yml (1)
17-19:fetch-depth: 0is unnecessary — considerfetch-depth: 1.The workflow creates new branches and pushes them; it doesn't need full history. A shallow clone (
fetch-depth: 1) would speed up the checkout step. You only need enough history to create a branch offmain.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/check-versions.yml around lines 17 - 19, The workflow currently uses actions/checkout with fetch-depth: 0 which does a full clone; update the checkout step (actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd) to use fetch-depth: 1 instead of 0 to perform a shallow clone (enough history to branch from main), keeping the with: fetch-depth key but changing its value to 1 to speed up the checkout.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/check-versions.yml:
- Line 161: The regex assigned to the variable pattern is over-escaped and will
not match strings like ${VAR:-value}; update the re.compile call (the pattern
variable) to use single backslashes in the raw string prefixes (e.g. r"\$\{" and
r"\}") instead of double-escaped backslashes so it builds a regex that matches
"${compose_var:-...}" correctly; specifically, change the concatenation around
re.escape(compose_var) so the prefix is r"\$\{" and the suffix is r":-([^}]+)\}"
(keep using re.escape(compose_var) unchanged) to restore correct matching.
- Around line 195-206: The script is creating feature branches from the current
(possibly dirty) HEAD via git checkout -b "$BRANCH_NAME" after running
./scripts/add-version.sh, which can cause branches to stack unintended changes;
before creating each branch (before git checkout -b "$BRANCH_NAME") ensure you
explicitly return to a clean main by running git checkout main and then git
fetch origin && git reset --hard origin/main (or equivalent) so the new branch
starts from the remote/main tip; apply this change around the code that calls
./scripts/add-version.sh, git add "$COMPOSE_FILE", git commit -m "chore:
${PACKAGE} ${UPSTREAM_VERSION}", and git push -u origin "$BRANCH_NAME" to
guarantee BRANCH_NAME is created cleanly and unrelated COMPOSE_FILE changes are
not carried forward.
In `@scripts/add-version.sh`:
- Around line 64-85: The heredoc invocation for the Python block is incorrect:
the arguments "$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION" are placed after the
PY terminator instead of on the python3 command line; move those three arguments
to the python3 invocation so the inline script receives them (i.e., call python3
- <<'PY' with the arguments immediately following python3, ensuring the script
uses sys.argv[1..3] for compose_file, compose_var, new_version), leaving the
rest of the block (pattern, updated, file writes) unchanged.
- Around line 35-52: The heredoc currently runs python3 without arguments so
sys.argv is empty; change the invocation to pass the two args to python3 before
the here-doc (e.g. OLD_VERSION=$(python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY'
... PY "$COMPOSE_FILE" "$COMPOSE_VAR" || echo "") ), or equivalently place the
"$COMPOSE_FILE" "$COMPOSE_VAR" immediately after python3 and before the <<'PY'
so that the python snippet (which uses sys.argv[1] and sys.argv[2]) receives the
expected arguments; keep the internal code using sys.argv[1]/sys.argv[2]
unchanged.
In `@scripts/check-versions.sh`:
- Around line 128-145: The heredoc feeding the inline Python to python3 is
terminated before the "$COMPOSE_FILE" "$COMPOSE_VAR" arguments are passed, so
LOCAL_VERSION is always empty; move the arguments into the heredoc invocation so
python3 receives sys.argv (i.e., ensure the python3 <<'PY' block is followed
immediately by the PY terminator and then call python3 with "$COMPOSE_FILE"
"$COMPOSE_VAR" inside the same command), or alternatively change to a
single-line python3 -c invocation that accepts "$COMPOSE_FILE" "$COMPOSE_VAR";
update the LOCAL_VERSION assignment to run python3 with the inline script
(reading compose_file and compose_var from sys.argv) so pattern.search and
match.group(1) execute correctly and the command returns the version.
- Around line 66-111: The python heredoc in the UPSTREAM_TAG assignment isn't
receiving the shell arguments because "$DOCKER_IMAGE" "$TAG_REGEX" are placed
after the heredoc terminator; move the arguments into the python3 command
invocation so they are passed as sys.argv (e.g., call python3 - "$DOCKER_IMAGE"
"$TAG_REGEX" <<'PY' ... PY), leaving the script body (including the normalize
function) unchanged and ensuring sys.argv[1] and sys.argv[2] map to DOCKER_IMAGE
and TAG_REGEX.
In `@scripts/validate-versions.sh`:
- Around line 41-58: The heredoc Python snippet that computes DEFAULT_VERSION is
currently invoked without passing "$COMPOSE_FILE" and "$COMPOSE_VAR" to python3
(so sys.argv[1]/[2] will be missing); fix by supplying those positional args on
the python3 command line (i.e., pass "$COMPOSE_FILE" "$COMPOSE_VAR" immediately
after the python3 invocation and before the <<'PY' heredoc token) for
DEFAULT_VERSION and apply the same change to the equivalent heredoc calls in
scripts/add-version.sh and scripts/check-versions.sh so the Python code can
safely access sys.argv[1] and sys.argv[2].
- Around line 34-38: The PATTERN assignment uses double quotes which cause
unintended parameter expansion; update the PATTERN construction so the literal
characters "\${" and ":-" are not expanded (e.g., build PATTERN using single
quotes for the fixed pieces or concatenate quoted literals and the variable: use
PATTERN='\\${'"${COMPOSE_VAR}"':-' or switch to a literal search like grep -F --
"$PATTERN" "$COMPOSE_FILE") and ensure the subsequent grep uses the appropriate
option (or proper escaping) so grep searches for the literal string; adjust
references to PATTERN, COMPOSE_VAR, COMPOSE_FILE, and the grep invocation
accordingly.
---
Nitpick comments:
In @.github/workflows/check-versions.yml:
- Around line 17-19: The workflow currently uses actions/checkout with
fetch-depth: 0 which does a full clone; update the checkout step
(actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd) to use fetch-depth:
1 instead of 0 to perform a shallow clone (enough history to branch from main),
keeping the with: fetch-depth key but changing its value to 1 to speed up the
checkout.
In `@scripts/add-version.sh`:
- Line 80: The replacement passed to pattern.sub uses new_version directly so
backslashes in new_version could be interpreted as regex backreferences; update
the call that assigns updated (the pattern.sub(..., new_version ...)) to escape
backslashes in new_version before passing it to pattern.sub (e.g., replace every
'\' with '\\') so the replacement string is treated literally; locate the
pattern.sub usage that assigns variable updated and apply
new_version.replace('\\','\\\\') (or equivalent) to the replacement argument.
.github/workflows/check-versions.yml
Outdated
| compose_file = sys.argv[1] | ||
| compose_var = sys.argv[2] | ||
|
|
||
| pattern = re.compile(r"\\$\\{" + re.escape(compose_var) + r":-([^}]+)\\}") |
There was a problem hiding this comment.
Broken regex — extra backslash escaping compared to the standalone scripts.
pattern = re.compile(r"\\$\\{" + re.escape(compose_var) + r":-([^}]+)\\}")In a Python raw string, \\ is literal \\ (two chars). The regex \\$\\{ means: match literal \, then end-of-string $, then literal \, then { — this will never match ${VAR:-...}.
The standalone scripts correctly use r"\$\{" (single backslash escaping $ and {). This line should match:
-pattern = re.compile(r"\\$\\{" + re.escape(compose_var) + r":-([^}]+)\\}")
+pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/check-versions.yml at line 161, The regex assigned to the
variable pattern is over-escaped and will not match strings like ${VAR:-value};
update the re.compile call (the pattern variable) to use single backslashes in
the raw string prefixes (e.g. r"\$\{" and r"\}") instead of double-escaped
backslashes so it builds a regex that matches "${compose_var:-...}" correctly;
specifically, change the concatenation around re.escape(compose_var) so the
prefix is r"\$\{" and the suffix is r":-([^}]+)\}" (keep using
re.escape(compose_var) unchanged) to restore correct matching.
| if ! ./scripts/add-version.sh "$PACKAGE" "$UPSTREAM_VERSION"; then | ||
| echo " WARNING: Failed to update $COMPOSE_FILE for $PACKAGE, skipping" | ||
| git checkout -- "$COMPOSE_FILE" || true | ||
| continue | ||
| fi | ||
|
|
||
| git checkout -b "$BRANCH_NAME" | ||
| git add "$COMPOSE_FILE" | ||
| git commit -m "chore: ${PACKAGE} ${UPSTREAM_VERSION}" | ||
|
|
||
| echo " Pushing branch..." | ||
| git push -u origin "$BRANCH_NAME" |
There was a problem hiding this comment.
Branch creation happens on a dirty working tree without resetting to main first.
git checkout -b "$BRANCH_NAME" (Line 201) creates the branch from whatever the current HEAD is. After the first package update, the working tree has uncommitted changes from add-version.sh. While git add and git commit follow, if a previous iteration's git checkout main (Line 225) fails or is skipped, subsequent packages would branch off a non-main commit, stacking unrelated changes.
Consider adding an explicit git checkout main and git reset --hard origin/main before creating each new branch to ensure a clean starting point.
Proposed fix
+ git checkout main
+ git pull origin main
+
git checkout -b "$BRANCH_NAME"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if ! ./scripts/add-version.sh "$PACKAGE" "$UPSTREAM_VERSION"; then | |
| echo " WARNING: Failed to update $COMPOSE_FILE for $PACKAGE, skipping" | |
| git checkout -- "$COMPOSE_FILE" || true | |
| continue | |
| fi | |
| git checkout -b "$BRANCH_NAME" | |
| git add "$COMPOSE_FILE" | |
| git commit -m "chore: ${PACKAGE} ${UPSTREAM_VERSION}" | |
| echo " Pushing branch..." | |
| git push -u origin "$BRANCH_NAME" | |
| if ! ./scripts/add-version.sh "$PACKAGE" "$UPSTREAM_VERSION"; then | |
| echo " WARNING: Failed to update $COMPOSE_FILE for $PACKAGE, skipping" | |
| git checkout -- "$COMPOSE_FILE" || true | |
| continue | |
| fi | |
| git checkout main | |
| git pull origin main | |
| git checkout -b "$BRANCH_NAME" | |
| git add "$COMPOSE_FILE" | |
| git commit -m "chore: ${PACKAGE} ${UPSTREAM_VERSION}" | |
| echo " Pushing branch..." | |
| git push -u origin "$BRANCH_NAME" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/check-versions.yml around lines 195 - 206, The script is
creating feature branches from the current (possibly dirty) HEAD via git
checkout -b "$BRANCH_NAME" after running ./scripts/add-version.sh, which can
cause branches to stack unintended changes; before creating each branch (before
git checkout -b "$BRANCH_NAME") ensure you explicitly return to a clean main by
running git checkout main and then git fetch origin && git reset --hard
origin/main (or equivalent) so the new branch starts from the remote/main tip;
apply this change around the code that calls ./scripts/add-version.sh, git add
"$COMPOSE_FILE", git commit -m "chore: ${PACKAGE} ${UPSTREAM_VERSION}", and git
push -u origin "$BRANCH_NAME" to guarantee BRANCH_NAME is created cleanly and
unrelated COMPOSE_FILE changes are not carried forward.
scripts/add-version.sh
Outdated
| OLD_VERSION=$(python3 - <<'PY' | ||
| import re | ||
| import sys | ||
|
|
||
| compose_file = sys.argv[1] | ||
| compose_var = sys.argv[2] | ||
|
|
||
| pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}") | ||
| with open(compose_file, "r") as fh: | ||
| data = fh.read() | ||
|
|
||
| match = pattern.search(data) | ||
| if not match: | ||
| sys.exit(1) | ||
|
|
||
| print(match.group(1)) | ||
| PY | ||
| "$COMPOSE_FILE" "$COMPOSE_VAR" || echo "") |
There was a problem hiding this comment.
Same heredoc argument bug — sys.argv will be empty.
Same issue as in validate-versions.sh: the "$COMPOSE_FILE" "$COMPOSE_VAR" on Line 52 are parsed as a separate command after the heredoc terminates, not as arguments to python3.
Proposed fix
-OLD_VERSION=$(python3 - <<'PY'
+OLD_VERSION=$(python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY'
import re
import sys
compose_file = sys.argv[1]
compose_var = sys.argv[2]
pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}")
with open(compose_file, "r") as fh:
data = fh.read()
match = pattern.search(data)
if not match:
sys.exit(1)
print(match.group(1))
PY
-"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
+|| echo "")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| OLD_VERSION=$(python3 - <<'PY' | |
| import re | |
| import sys | |
| compose_file = sys.argv[1] | |
| compose_var = sys.argv[2] | |
| pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}") | |
| with open(compose_file, "r") as fh: | |
| data = fh.read() | |
| match = pattern.search(data) | |
| if not match: | |
| sys.exit(1) | |
| print(match.group(1)) | |
| PY | |
| "$COMPOSE_FILE" "$COMPOSE_VAR" || echo "") | |
| OLD_VERSION=$(python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY' | |
| import re | |
| import sys | |
| compose_file = sys.argv[1] | |
| compose_var = sys.argv[2] | |
| pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}") | |
| with open(compose_file, "r") as fh: | |
| data = fh.read() | |
| match = pattern.search(data) | |
| if not match: | |
| sys.exit(1) | |
| print(match.group(1)) | |
| PY | |
| || echo "") |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/add-version.sh` around lines 35 - 52, The heredoc currently runs
python3 without arguments so sys.argv is empty; change the invocation to pass
the two args to python3 before the here-doc (e.g. OLD_VERSION=$(python3 -
"$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY' ... PY "$COMPOSE_FILE" "$COMPOSE_VAR" ||
echo "") ), or equivalently place the "$COMPOSE_FILE" "$COMPOSE_VAR" immediately
after python3 and before the <<'PY' so that the python snippet (which uses
sys.argv[1] and sys.argv[2]) receives the expected arguments; keep the internal
code using sys.argv[1]/sys.argv[2] unchanged.
scripts/add-version.sh
Outdated
| if ! python3 - <<'PY' | ||
| import re | ||
| import sys | ||
|
|
||
| compose_file = sys.argv[1] | ||
| compose_var = sys.argv[2] | ||
| new_version = sys.argv[3] | ||
|
|
||
| pattern = re.compile(r"(\$\{" + re.escape(compose_var) + r":-)([^}]+)(\})") | ||
| with open(compose_file, "r") as fh: | ||
| data = fh.read() | ||
|
|
||
| if not pattern.search(data): | ||
| print("compose var not found", file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
| updated = pattern.sub(r"\1" + new_version + r"\3", data, count=1) | ||
|
|
||
| with open(compose_file, "w") as fh: | ||
| fh.write(updated) | ||
| PY | ||
| "$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION"; then |
There was a problem hiding this comment.
Same heredoc argument bug in the update block.
Line 85 places "$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION" after the heredoc terminator. They need to be on the python3 command line.
Proposed fix
-if ! python3 - <<'PY'
+if ! python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION" <<'PY'
import re
import sys
compose_file = sys.argv[1]
compose_var = sys.argv[2]
new_version = sys.argv[3]
pattern = re.compile(r"(\$\{" + re.escape(compose_var) + r":-)([^}]+)(\})")
with open(compose_file, "r") as fh:
data = fh.read()
if not pattern.search(data):
print("compose var not found", file=sys.stderr)
sys.exit(1)
updated = pattern.sub(r"\1" + new_version + r"\3", data, count=1)
with open(compose_file, "w") as fh:
fh.write(updated)
PY
-"$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION"; then
+; then📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if ! python3 - <<'PY' | |
| import re | |
| import sys | |
| compose_file = sys.argv[1] | |
| compose_var = sys.argv[2] | |
| new_version = sys.argv[3] | |
| pattern = re.compile(r"(\$\{" + re.escape(compose_var) + r":-)([^}]+)(\})") | |
| with open(compose_file, "r") as fh: | |
| data = fh.read() | |
| if not pattern.search(data): | |
| print("compose var not found", file=sys.stderr) | |
| sys.exit(1) | |
| updated = pattern.sub(r"\1" + new_version + r"\3", data, count=1) | |
| with open(compose_file, "w") as fh: | |
| fh.write(updated) | |
| PY | |
| "$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION"; then | |
| if ! python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION" <<'PY' | |
| import re | |
| import sys | |
| compose_file = sys.argv[1] | |
| compose_var = sys.argv[2] | |
| new_version = sys.argv[3] | |
| pattern = re.compile(r"(\$\{" + re.escape(compose_var) + r":-)([^}]+)(\})") | |
| with open(compose_file, "r") as fh: | |
| data = fh.read() | |
| if not pattern.search(data): | |
| print("compose var not found", file=sys.stderr) | |
| sys.exit(1) | |
| updated = pattern.sub(r"\1" + new_version + r"\3", data, count=1) | |
| with open(compose_file, "w") as fh: | |
| fh.write(updated) | |
| PY | |
| ; then |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/add-version.sh` around lines 64 - 85, The heredoc invocation for the
Python block is incorrect: the arguments "$COMPOSE_FILE" "$COMPOSE_VAR"
"$NEW_VERSION" are placed after the PY terminator instead of on the python3
command line; move those three arguments to the python3 invocation so the inline
script receives them (i.e., call python3 - <<'PY' with the arguments immediately
following python3, ensuring the script uses sys.argv[1..3] for compose_file,
compose_var, new_version), leaving the rest of the block (pattern, updated, file
writes) unchanged.
scripts/check-versions.sh
Outdated
| UPSTREAM_TAG=$(python3 - <<'PY' | ||
| import json | ||
| import re | ||
| import sys | ||
| import urllib.request | ||
|
|
||
| image = sys.argv[1] | ||
| regex = re.compile(sys.argv[2]) | ||
|
|
||
| page = 1 | ||
| page_size = 100 | ||
| found = [] | ||
|
|
||
| while True: | ||
| url = f"https://hub.docker.com/v2/repositories/{image}/tags?page={page}&page_size={page_size}" | ||
| with urllib.request.urlopen(url) as resp: | ||
| data = json.loads(resp.read().decode("utf-8")) | ||
|
|
||
| for item in data.get("results", []): | ||
| tag = item.get("name", "") | ||
| if regex.match(tag): | ||
| found.append(tag) | ||
|
|
||
| if not data.get("next"): | ||
| break | ||
| page += 1 | ||
|
|
||
| if not found: | ||
| sys.exit(1) | ||
|
|
||
| def normalize(tag): | ||
| if tag.startswith("v"): | ||
| tag = tag[1:] | ||
| parts = re.split(r"[.-]", tag) | ||
| key = [] | ||
| for part in parts: | ||
| if part.isdigit(): | ||
| key.append(int(part)) | ||
| else: | ||
| key.append(part) | ||
| return key | ||
|
|
||
| found.sort(key=normalize) | ||
| print(found[-1]) | ||
| PY | ||
| "$DOCKER_IMAGE" "$TAG_REGEX" || echo "") |
There was a problem hiding this comment.
Same heredoc argument bug — Docker Hub Python snippet won't receive arguments.
Line 111: "$DOCKER_IMAGE" "$TAG_REGEX" appears after the heredoc terminator and won't be passed to python3.
Proposed fix
- UPSTREAM_TAG=$(python3 - <<'PY'
+ UPSTREAM_TAG=$(python3 - "$DOCKER_IMAGE" "$TAG_REGEX" <<'PY'
...
PY
-"$DOCKER_IMAGE" "$TAG_REGEX" || echo "")
+|| echo "")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| UPSTREAM_TAG=$(python3 - <<'PY' | |
| import json | |
| import re | |
| import sys | |
| import urllib.request | |
| image = sys.argv[1] | |
| regex = re.compile(sys.argv[2]) | |
| page = 1 | |
| page_size = 100 | |
| found = [] | |
| while True: | |
| url = f"https://hub.docker.com/v2/repositories/{image}/tags?page={page}&page_size={page_size}" | |
| with urllib.request.urlopen(url) as resp: | |
| data = json.loads(resp.read().decode("utf-8")) | |
| for item in data.get("results", []): | |
| tag = item.get("name", "") | |
| if regex.match(tag): | |
| found.append(tag) | |
| if not data.get("next"): | |
| break | |
| page += 1 | |
| if not found: | |
| sys.exit(1) | |
| def normalize(tag): | |
| if tag.startswith("v"): | |
| tag = tag[1:] | |
| parts = re.split(r"[.-]", tag) | |
| key = [] | |
| for part in parts: | |
| if part.isdigit(): | |
| key.append(int(part)) | |
| else: | |
| key.append(part) | |
| return key | |
| found.sort(key=normalize) | |
| print(found[-1]) | |
| PY | |
| "$DOCKER_IMAGE" "$TAG_REGEX" || echo "") | |
| UPSTREAM_TAG=$(python3 - "$DOCKER_IMAGE" "$TAG_REGEX" <<'PY' | |
| import json | |
| import re | |
| import sys | |
| import urllib.request | |
| image = sys.argv[1] | |
| regex = re.compile(sys.argv[2]) | |
| page = 1 | |
| page_size = 100 | |
| found = [] | |
| while True: | |
| url = f"https://hub.docker.com/v2/repositories/{image}/tags?page={page}&page_size={page_size}" | |
| with urllib.request.urlopen(url) as resp: | |
| data = json.loads(resp.read().decode("utf-8")) | |
| for item in data.get("results", []): | |
| tag = item.get("name", "") | |
| if regex.match(tag): | |
| found.append(tag) | |
| if not data.get("next"): | |
| break | |
| page += 1 | |
| if not found: | |
| sys.exit(1) | |
| def normalize(tag): | |
| if tag.startswith("v"): | |
| tag = tag[1:] | |
| parts = re.split(r"[.-]", tag) | |
| key = [] | |
| for part in parts: | |
| if part.isdigit(): | |
| key.append(int(part)) | |
| else: | |
| key.append(part) | |
| return key | |
| found.sort(key=normalize) | |
| print(found[-1]) | |
| PY | |
| || echo "") |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/check-versions.sh` around lines 66 - 111, The python heredoc in the
UPSTREAM_TAG assignment isn't receiving the shell arguments because
"$DOCKER_IMAGE" "$TAG_REGEX" are placed after the heredoc terminator; move the
arguments into the python3 command invocation so they are passed as sys.argv
(e.g., call python3 - "$DOCKER_IMAGE" "$TAG_REGEX" <<'PY' ... PY), leaving the
script body (including the normalize function) unchanged and ensuring
sys.argv[1] and sys.argv[2] map to DOCKER_IMAGE and TAG_REGEX.
scripts/check-versions.sh
Outdated
| LOCAL_VERSION=$(python3 - <<'PY' | ||
| import re | ||
| import sys | ||
|
|
||
| compose_file = sys.argv[1] | ||
| compose_var = sys.argv[2] | ||
|
|
||
| pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}") | ||
| with open(compose_file, "r") as fh: | ||
| data = fh.read() | ||
|
|
||
| match = pattern.search(data) | ||
| if not match: | ||
| sys.exit(1) | ||
|
|
||
| print(match.group(1)) | ||
| PY | ||
| "$COMPOSE_FILE" "$COMPOSE_VAR" || echo "") |
There was a problem hiding this comment.
Same heredoc argument bug — local version extraction will fail.
Line 145: "$COMPOSE_FILE" "$COMPOSE_VAR" appears after the heredoc terminator.
Proposed fix
- LOCAL_VERSION=$(python3 - <<'PY'
+ LOCAL_VERSION=$(python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY'
...
PY
-"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
+|| echo "")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/check-versions.sh` around lines 128 - 145, The heredoc feeding the
inline Python to python3 is terminated before the "$COMPOSE_FILE" "$COMPOSE_VAR"
arguments are passed, so LOCAL_VERSION is always empty; move the arguments into
the heredoc invocation so python3 receives sys.argv (i.e., ensure the python3
<<'PY' block is followed immediately by the PY terminator and then call python3
with "$COMPOSE_FILE" "$COMPOSE_VAR" inside the same command), or alternatively
change to a single-line python3 -c invocation that accepts "$COMPOSE_FILE"
"$COMPOSE_VAR"; update the LOCAL_VERSION assignment to run python3 with the
inline script (reading compose_file and compose_var from sys.argv) so
pattern.search and match.group(1) execute correctly and the command returns the
version.
scripts/validate-versions.sh
Outdated
| PATTERN="\\${${COMPOSE_VAR}:-" | ||
| if ! grep -q "$PATTERN" "$COMPOSE_FILE"; then | ||
| echo "ERROR: ${COMPOSE_VAR} not found in $COMPOSE_FILE" | ||
| ERRORS=$((ERRORS + 1)) | ||
| continue |
There was a problem hiding this comment.
Broken shell quoting in grep pattern — script will error or misbehave.
PATTERN="\\${${COMPOSE_VAR}:-" attempts to construct a literal string like \${CARDANO_NODE_VERSION:-, but inside double quotes, ${…} triggers parameter expansion. Bash will try to interpret ${${COMPOSE_VAR}:-" as a (malformed) nested expansion, leading to a parse error or unexpected behavior. This matches the ShellCheck SC1078/SC1073 warnings.
Use single quotes to avoid expansion for the literal parts:
Proposed fix
- PATTERN="\\${${COMPOSE_VAR}:-"
- if ! grep -q "$PATTERN" "$COMPOSE_FILE"; then
+ PATTERN='\$\{'"${COMPOSE_VAR}"':-'
+ if ! grep -q "$PATTERN" "$COMPOSE_FILE"; thenAlternatively, use grep -F with a literal (non-regex) match:
- PATTERN="\\${${COMPOSE_VAR}:-"
- if ! grep -q "$PATTERN" "$COMPOSE_FILE"; then
+ PATTERN='${'"${COMPOSE_VAR}"':-'
+ if ! grep -Fq "$PATTERN" "$COMPOSE_FILE"; then📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| PATTERN="\\${${COMPOSE_VAR}:-" | |
| if ! grep -q "$PATTERN" "$COMPOSE_FILE"; then | |
| echo "ERROR: ${COMPOSE_VAR} not found in $COMPOSE_FILE" | |
| ERRORS=$((ERRORS + 1)) | |
| continue | |
| PATTERN='\$\{'"${COMPOSE_VAR}"':-' | |
| if ! grep -q "$PATTERN" "$COMPOSE_FILE"; then | |
| echo "ERROR: ${COMPOSE_VAR} not found in $COMPOSE_FILE" | |
| ERRORS=$((ERRORS + 1)) | |
| continue |
🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 35-35: Did you forget to close this double quoted string?
(SC1078)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/validate-versions.sh` around lines 34 - 38, The PATTERN assignment
uses double quotes which cause unintended parameter expansion; update the
PATTERN construction so the literal characters "\${" and ":-" are not expanded
(e.g., build PATTERN using single quotes for the fixed pieces or concatenate
quoted literals and the variable: use PATTERN='\\${'"${COMPOSE_VAR}"':-' or
switch to a literal search like grep -F -- "$PATTERN" "$COMPOSE_FILE") and
ensure the subsequent grep uses the appropriate option (or proper escaping) so
grep searches for the literal string; adjust references to PATTERN, COMPOSE_VAR,
COMPOSE_FILE, and the grep invocation accordingly.
scripts/validate-versions.sh
Outdated
| DEFAULT_VERSION=$(python3 - <<'PY' | ||
| import re | ||
| import sys | ||
|
|
||
| compose_file = sys.argv[1] | ||
| compose_var = sys.argv[2] | ||
|
|
||
| pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}") | ||
| with open(compose_file, "r") as fh: | ||
| data = fh.read() | ||
|
|
||
| match = pattern.search(data) | ||
| if not match: | ||
| sys.exit(1) | ||
|
|
||
| print(match.group(1)) | ||
| PY | ||
| "$COMPOSE_FILE" "$COMPOSE_VAR" || echo "") |
There was a problem hiding this comment.
Heredoc arguments not passed to python3 — sys.argv[1] will raise IndexError.
The positional arguments "$COMPOSE_FILE" "$COMPOSE_VAR" appear on Line 58, after the heredoc terminator PY on Line 57. Bash treats them as a separate command, not as arguments to python3. The Python script accesses sys.argv[1] and sys.argv[2], which won't exist, causing an IndexError.
Place the arguments on the initial command line, before the heredoc redirect:
Proposed fix
- DEFAULT_VERSION=$(python3 - <<'PY'
+ DEFAULT_VERSION=$(python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY'
import re
import sys
compose_file = sys.argv[1]
compose_var = sys.argv[2]
pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}")
with open(compose_file, "r") as fh:
data = fh.read()
match = pattern.search(data)
if not match:
sys.exit(1)
print(match.group(1))
PY
-"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
+|| echo "")This same bug exists in scripts/add-version.sh (Lines 35–52 and 64–85) and scripts/check-versions.sh (Lines 128–145) — all heredoc invocations need the same fix.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/validate-versions.sh` around lines 41 - 58, The heredoc Python
snippet that computes DEFAULT_VERSION is currently invoked without passing
"$COMPOSE_FILE" and "$COMPOSE_VAR" to python3 (so sys.argv[1]/[2] will be
missing); fix by supplying those positional args on the python3 command line
(i.e., pass "$COMPOSE_FILE" "$COMPOSE_VAR" immediately after the python3
invocation and before the <<'PY' heredoc token) for DEFAULT_VERSION and apply
the same change to the equivalent heredoc calls in scripts/add-version.sh and
scripts/check-versions.sh so the Python code can safely access sys.argv[1] and
sys.argv[2].
There was a problem hiding this comment.
3 issues found across 7 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name=".github/workflows/check-versions.yml">
<violation number="1" location=".github/workflows/check-versions.yml:68">
P2: The default TAG_REGEX incorrectly escapes the dot (`\\.`), so it won’t match normal semver tags like `1.2.3`. This causes `UPSTREAM_TAG` to stay empty and the workflow to skip updates. Use a single escaped dot in the regex.</violation>
</file>
<file name="scripts/check-versions.sh">
<violation number="1" location="scripts/check-versions.sh:111">
P2: The Docker Hub Python helper never receives its CLI arguments because they’re placed after the heredoc terminator. This causes the script to fall back to an empty UPSTREAM_TAG and skip updates for Docker Hub images.</violation>
<violation number="2" location="scripts/check-versions.sh:145">
P2: The compose version lookup never receives its CLI arguments because they’re placed after the heredoc terminator, so local versions are always treated as missing.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
.github/workflows/check-versions.yml
Outdated
| fi | ||
|
|
||
| if [[ -z "$TAG_REGEX" ]]; then | ||
| TAG_REGEX='^[0-9]+(\\.[0-9]+){2,3}$' |
There was a problem hiding this comment.
P2: The default TAG_REGEX incorrectly escapes the dot (\\.), so it won’t match normal semver tags like 1.2.3. This causes UPSTREAM_TAG to stay empty and the workflow to skip updates. Use a single escaped dot in the regex.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/check-versions.yml, line 68:
<comment>The default TAG_REGEX incorrectly escapes the dot (`\\.`), so it won’t match normal semver tags like `1.2.3`. This causes `UPSTREAM_TAG` to stay empty and the workflow to skip updates. Use a single escaped dot in the regex.</comment>
<file context>
@@ -0,0 +1,233 @@
+ fi
+
+ if [[ -z "$TAG_REGEX" ]]; then
+ TAG_REGEX='^[0-9]+(\\.[0-9]+){2,3}$'
+ fi
+
</file context>
scripts/check-versions.sh
Outdated
|
|
||
| print(match.group(1)) | ||
| PY | ||
| "$COMPOSE_FILE" "$COMPOSE_VAR" || echo "") |
There was a problem hiding this comment.
P2: The compose version lookup never receives its CLI arguments because they’re placed after the heredoc terminator, so local versions are always treated as missing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/check-versions.sh, line 145:
<comment>The compose version lookup never receives its CLI arguments because they’re placed after the heredoc terminator, so local versions are always treated as missing.</comment>
<file context>
@@ -0,0 +1,183 @@
+
+print(match.group(1))
+PY
+"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
+
+ if [[ -z "$LOCAL_VERSION" ]]; then
</file context>
scripts/check-versions.sh
Outdated
| found.sort(key=normalize) | ||
| print(found[-1]) | ||
| PY | ||
| "$DOCKER_IMAGE" "$TAG_REGEX" || echo "") |
There was a problem hiding this comment.
P2: The Docker Hub Python helper never receives its CLI arguments because they’re placed after the heredoc terminator. This causes the script to fall back to an empty UPSTREAM_TAG and skip updates for Docker Hub images.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/check-versions.sh, line 111:
<comment>The Docker Hub Python helper never receives its CLI arguments because they’re placed after the heredoc terminator. This causes the script to fall back to an empty UPSTREAM_TAG and skip updates for Docker Hub images.</comment>
<file context>
@@ -0,0 +1,183 @@
+found.sort(key=normalize)
+print(found[-1])
+PY
+"$DOCKER_IMAGE" "$TAG_REGEX" || echo "")
+
+ if [[ -z "$UPSTREAM_TAG" ]]; then
</file context>
Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
dc40451 to
37e562e
Compare
|
@cubic-dev-ai review |
@wolf31o2 I have started the AI code review. It will take a few minutes to complete. |
Summary by cubic
Automates container image version maintenance with a weekly GitHub Action that detects upstream updates and opens PRs to refresh docker-compose defaults. Adds simple scripts and Makefile targets for local checks, validation, and manual bumps.
Written for commit 37e562e. Summary will update on new commits.
Summary by CodeRabbit
Release Notes
New Features
Documentation