Skip to content

Pin and verify SHAs for CI image dependencies#167

Open
ay901246 wants to merge 2 commits intocloudfoundry:windows-2019from
ay901246:fix-unpinned-dependencies
Open

Pin and verify SHAs for CI image dependencies#167
ay901246 wants to merge 2 commits intocloudfoundry:windows-2019from
ay901246:fix-unpinned-dependencies

Conversation

@ay901246
Copy link
Copy Markdown
Contributor

Motivation

Currently, the CI pipeline dynamically fetches the "latest" release binaries (BOSH CLI, meta4, yq, etc.) via the GitHub API every time the Docker image is built. This approach has a few drawbacks:

  1. Security: We are not verifying the integrity of the downloaded binaries (no SHA-256 validation).
  2. Reliability: Unannounced upstream changes or GitHub API rate limits can instantly break our main CI pipeline.
  3. Reproducibility: CI builds are not deterministic.

Key Changes

This PR overhauls how we handle CI image dependencies by pinning them explicitly and automating their updates:

  • Introduced dependencies.json: All CI binary URLs and SHA-256 hashes are now hardcoded in a single source of truth.
  • Dockerfile SHA Validation: Updated the Dockerfile to calculate and verify the sha256sum of every downloaded binary before installation. The build will now fail fast if a checksum mismatches.
  • Automated Bump Workflow: Added a weekly GitHub Action (bump-binaries.yml) that runs a bash script to query the latest releases, calculate their new SHAs, and automatically open a PR with the updated dependencies.json.
  • Pre-flight Checks: The GitHub Action includes a dry-run docker build step. It will only open a PR if the new URLs and hashes successfully build the Docker image, preventing broken PRs from being submitted.

Testing Performed

  • Local execution of ci/scripts/bump-binaries.sh successfully updates the JSON.
  • The GitHub Action successfully runs a mock Docker build using the updated arguments to verify the syntax, URLs, and SHAs before attempting to create a PR.

Example of it working on my fork: https://github.com/ay901246/bosh-windows-stemcell-builder/actions/workflows/bump-binaries.yml

@ay901246
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@ragaskar
Copy link
Copy Markdown
Contributor

There might be some extremely subtle edges I'm not seeing here, but this lgtm.

I'm going to hold off an official thumbs up to give other folks a chance to review as well.

@rkoster
Copy link
Copy Markdown
Contributor

rkoster commented Apr 23, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Walkthrough

This change introduces automated binary dependency management with SHA-256 integrity verification. A new GitHub Actions workflow executes a bash script that automatically checks for updated releases of six tools (bosh-cli, meta4-cli, yq, ruby-install, golangci-lint, govc) and updates their download URLs and SHA-256 checksums in a new JSON manifest file. The Docker image build process is updated to use this manifest instead of dynamic GitHub API queries, and the Dockerfile now validates downloaded artifacts against their checksums before installation. A pull request is opened automatically when dependencies are updated.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: pinning CI dependencies and adding SHA verification.
Description check ✅ Passed The description is well-written and directly related to the changeset, covering motivation, key changes, and testing performed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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/bump-binaries.yml:
- Around line 31-32: Remove the mock azstemcell file after the Docker build and
restrict the PR action to only include the intended file: add a cleanup step
that runs rm -f ci/docker/azstemcell (to remove the file created by the touch
ci/docker/azstemcell) immediately after the Docker build step, and update the PR
creation action to include add-paths: ci/docker/dependencies.json so only
dependencies.json is added to the PR; locate the touch call in
.github/workflows/bump-binaries.yml and add the rm -f cleanup and the add-paths
setting in the PR step that references the created artifacts.

In `@ci/scripts/bump-binaries.sh`:
- Around line 75-77: The jq invocation should pass url/sha (and the dynamic key)
via --arg instead of interpolating shell variables into the program; update the
invocation that currently uses jq ".${key}.url = \"${url}\" | .${key}.sha256 =
\"${sha}\"" to use --arg key "$key" --arg url "$url" --arg sha "$sha" and change
the program to use the jq variables (e.g. .[$key].url = $url | .[$key].sha256 =
$sha) writing to TMP_FILE and then mv TMP_FILE to DEPS_FILE as before.
- Around line 51-54: The current extraction uses head which can cause jq to
receive a SIGPIPE under set -o pipefail; replace the pipeline that uses head
with a jq-only selection that returns the first matching asset inside jq.
Specifically, update the assignment to url (where release_json and pattern are
used) to use a jq expression such as: ".assets | map(select(.name |
test(\"${pattern}\"))) | .[0].browser_download_url" (and keep -r) so the first
match is selected inside jq and no external head/cutting of the pipe is needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a156fd5d-9cc0-4372-8c54-69f7a6e4e9b7

📥 Commits

Reviewing files that changed from the base of the PR and between 8c4d2e7 and 61b500d.

📒 Files selected for processing (5)
  • .github/workflows/bump-binaries.yml
  • ci/docker/Dockerfile
  • ci/docker/dependencies.json
  • ci/scripts/bump-binaries.sh
  • ci/tasks/build-docker-args/run.sh

Comment on lines +31 to +32
# Mock the azstemcell binary for the docker build context
touch ci/docker/azstemcell
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether the workflow's mock Docker context file would be excluded from commits.

set -euo pipefail

echo "Tracked status:"
git ls-files --stage -- ci/docker/azstemcell || true

echo "Ignore status:"
git check-ignore -v ci/docker/azstemcell || true

echo "Relevant ignore rules:"
rg -n -C2 '(^|/)azstemcell$|ci/docker/azstemcell' .gitignore .github ci || true

Repository: cloudfoundry/bosh-windows-stemcell-builder

Length of output: 1347


🏁 Script executed:

cat -n .github/workflows/bump-binaries.yml | sed -n '25,85p'

Repository: cloudfoundry/bosh-windows-stemcell-builder

Length of output: 3314


Remove the mock azstemcell file before creating the PR.

Line 32 creates ci/docker/azstemcell for the Docker build, but it remains as an untracked, unignored file when the PR creation step executes. Without cleanup or path restriction, it will be committed to the pull request alongside the intended dependencies.json changes.

Add rm -f ci/docker/azstemcell after the Docker build step (after line 67) and add add-paths: ci/docker/dependencies.json to the PR action to ensure only the intended file is committed.

Proposed fix
           docker build \
             --build-arg BOSH_CLI_URL="$BOSH_CLI_URL" \
             --build-arg BOSH_CLI_SHA256="$BOSH_CLI_SHA" \
             --build-arg META4_CLI_URL="$META4_CLI_URL" \
             --build-arg META4_CLI_SHA256="$META4_CLI_SHA" \
             --build-arg YQ_CLI_URL="$YQ_CLI_URL" \
             --build-arg YQ_CLI_SHA256="$YQ_CLI_SHA" \
             --build-arg RUBY_INSTALL_URL="$RUBY_INSTALL_URL" \
             --build-arg RUBY_INSTALL_SHA256="$RUBY_INSTALL_SHA" \
             --build-arg GOLANGCI_LINT_INSTALL_URL="$GOLANGCI_LINT_URL" \
             --build-arg GOLANGCI_LINT_INSTALL_SHA256="$GOLANGCI_LINT_SHA" \
             --build-arg GOVC_INSTALL_URL="$GOVC_URL" \
             --build-arg GOVC_INSTALL_SHA256="$GOVC_SHA" \
             --build-arg RUBY_VERSION="$RUBY_VERSION" \
             --build-arg GEM_HOME="$GEM_HOME" \
             -f ci/docker/Dockerfile ci/docker/
+
+          rm -f ci/docker/azstemcell

       - name: Create Pull Request
         uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
+          add-paths: ci/docker/dependencies.json
           commit-message: "Update binary dependencies in dependencies.json"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/bump-binaries.yml around lines 31 - 32, Remove the mock
azstemcell file after the Docker build and restrict the PR action to only
include the intended file: add a cleanup step that runs rm -f
ci/docker/azstemcell (to remove the file created by the touch
ci/docker/azstemcell) immediately after the Docker build step, and update the PR
creation action to include add-paths: ci/docker/dependencies.json so only
dependencies.json is added to the PR; locate the touch call in
.github/workflows/bump-binaries.yml and add the rm -f cleanup and the add-paths
setting in the PR step that references the created artifacts.

Comment on lines +51 to +54
# 2. Extract the URL
local url
url=$(echo "${release_json}" | jq -r ".assets[] | select(.name | test(\"${pattern}\")) | .browser_download_url" | head -n 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid head under pipefail for asset selection.

If multiple assets match, head -n 1 can close the pipe early and make jq fail with SIGPIPE under set -o pipefail, aborting the bump workflow. Select the first match inside jq instead.

Proposed fix
-  url=$(echo "${release_json}" | jq -r ".assets[] | select(.name | test(\"${pattern}\")) | .browser_download_url" | head -n 1)
+  url=$(echo "${release_json}" | jq -r --arg pattern "${pattern}" '
+    first(.assets[] | select(.name | test($pattern)) | .browser_download_url) // empty
+  ')
📝 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.

Suggested change
# 2. Extract the URL
local url
url=$(echo "${release_json}" | jq -r ".assets[] | select(.name | test(\"${pattern}\")) | .browser_download_url" | head -n 1)
# 2. Extract the URL
local url
url=$(echo "${release_json}" | jq -r --arg pattern "${pattern}" '
first(.assets[] | select(.name | test($pattern)) | .browser_download_url) // empty
')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ci/scripts/bump-binaries.sh` around lines 51 - 54, The current extraction
uses head which can cause jq to receive a SIGPIPE under set -o pipefail; replace
the pipeline that uses head with a jq-only selection that returns the first
matching asset inside jq. Specifically, update the assignment to url (where
release_json and pattern are used) to use a jq expression such as: ".assets |
map(select(.name | test(\"${pattern}\"))) | .[0].browser_download_url" (and keep
-r) so the first match is selected inside jq and no external head/cutting of the
pipe is needed.

Comment on lines +75 to +77
echo " Updating ${DEPS_FILE}..."
jq ".${key}.url = \"${url}\" | .${key}.sha256 = \"${sha}\"" "${DEPS_FILE}" > "${TMP_FILE}"
mv "${TMP_FILE}" "${DEPS_FILE}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Pass updated values to jq with --arg.

Directly interpolating ${url} into the jq program makes the update fragile if GitHub asset URLs ever contain quoting or escape characters.

Proposed fix
-  jq ".${key}.url = \"${url}\" | .${key}.sha256 = \"${sha}\"" "${DEPS_FILE}" > "${TMP_FILE}"
+  jq --arg key "${key}" --arg url "${url}" --arg sha "${sha}" '
+    .[$key].url = $url | .[$key].sha256 = $sha
+  ' "${DEPS_FILE}" > "${TMP_FILE}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ci/scripts/bump-binaries.sh` around lines 75 - 77, The jq invocation should
pass url/sha (and the dynamic key) via --arg instead of interpolating shell
variables into the program; update the invocation that currently uses jq
".${key}.url = \"${url}\" | .${key}.sha256 = \"${sha}\"" to use --arg key "$key"
--arg url "$url" --arg sha "$sha" and change the program to use the jq variables
(e.g. .[$key].url = $url | .[$key].sha256 = $sha) writing to TMP_FILE and then
mv TMP_FILE to DEPS_FILE as before.

@github-project-automation github-project-automation Bot moved this from Inbox to Waiting for Changes | Open for Contribution in Foundational Infrastructure Working Group Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Waiting for Changes | Open for Contribution

Development

Successfully merging this pull request may close these issues.

3 participants