Repository navigation
ci(musl): build the Alpine image from a pinned Dockerfile Dependabot can move #1107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'stash': patch | ||
| --- | ||
|
|
||
| The supply-chain skill names every Dependabot ecosystem the repository monitors, including the Go module and the Docker image the musl binaries are built in. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # Builds the linux-x64-musl binaries of @cipherstash/auth and | ||
| # @cipherstash/protect-ffi. Alpine is a musl system, so its compiler links | ||
| # musl; the Ubuntu runner's compiler links glibc, and the result does not load | ||
| # on musl. | ||
| # | ||
| # Pinned because the output is published with provenance. Dependabot moves the | ||
| # digest; nothing moves the package versions, and Alpine drops a version from | ||
| # its index when it publishes the next one, so a pin can stop resolving at any | ||
| # time. musl-build-image.yml builds this file weekly so that is a failed check, | ||
| # not a failed release. Re-pin from the FROM image with | ||
| # `apk update >/dev/null && apk search --exact <packages>`. | ||
| FROM node:22-alpine@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402 | ||
|
|
||
| # build-base is a metapackage whose compiler and linker dependencies carry no | ||
| # version, so gcc, g++, binutils, musl-dev and make are pinned beside it. | ||
| RUN apk add --no-cache \ | ||
| binutils=2.45.1-r1 \ | ||
| build-base=0.5-r4 \ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change before merge: Impact: Two builds of one commit can still use different compilers. That is the problem #1042 describes. Evidence: In the pinned image (Alpine 3.24.2): Fix: Also pin the packages that compile and link the binary: RUN apk add --no-cache \
binutils=2.45.1-r1 \
build-base=0.5-r4 \
cmake=4.2.3-r0 \
curl=8.22.0-r0 \
g++=15.2.0-r5 \
gcc=15.2.0-r5 \
git=2.54.0-r0 \
linux-headers=7.0.0-r1 \
make=4.4.1-r4 \
musl-dev=1.2.6-r2 \
perl=5.42.2-r1 \
rustup=1.29.0-r0Each new pin can stop resolving, like the existing ones. Found by 1 model: claude
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in ee87669: binutils, g++, gcc, make and musl-dev pinned at the versions you listed (image rebuilt locally, 46 packages, OK), and all five added to |
||
| cmake=4.2.3-r0 \ | ||
| curl=8.22.0-r0 \ | ||
| g++=15.2.0-r5 \ | ||
| gcc=15.2.0-r5 \ | ||
| git=2.54.0-r0 \ | ||
| linux-headers=7.0.0-r1 \ | ||
| make=4.4.1-r4 \ | ||
| musl-dev=1.2.6-r2 \ | ||
| perl=5.42.2-r1 \ | ||
| rustup=1.29.0-r0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,7 +47,10 @@ jobs: | |
| with: | ||
| ref: ${{ inputs.ref }} | ||
| persist-credentials: false | ||
| sparse-checkout: scripts | ||
| # The musl smoke test reads the Dockerfile's FROM line. | ||
| sparse-checkout: | | ||
| scripts | ||
| .github/docker/musl-build | ||
|
|
||
| - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | ||
| with: | ||
|
|
@@ -131,12 +134,14 @@ jobs: | |
|
|
||
| # The host step above installs only linux-x64-gnu, the runner's own | ||
| # platform. The musl binary loads only where musl is the C library, so it | ||
| # is installed and loaded inside Alpine, from the image the build uses. | ||
| # is installed and loaded inside Alpine, on the base image the build | ||
| # image is made from: plain Node.js on musl, as a user's container is. | ||
| # Read from the Dockerfile so there is one digest for Dependabot to move. | ||
| - name: Smoke-test the musl artifact inside Alpine | ||
| env: | ||
| ALPINE_NODE_IMAGE: node:22-alpine@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402 | ||
| run: | | ||
| set -euo pipefail | ||
| ALPINE_NODE_IMAGE=$(sed -n 's/^FROM //p' .github/docker/musl-build/Dockerfile) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix before merge: The Impact: Every dispatched Evidence: The
Fix: Add the Dockerfile directory to the sparse checkout at line 50: sparse-checkout: |
scripts
.github/docker/musl-buildWith this list, To stop the same mistake later, add a check to const job = Object.values(wf.jobs).find((j) =>
(j?.steps ?? []).some((s) => String(s?.run ?? '').includes(DOCKERFILE)),
)
const checkout = job.steps.find((s) =>
String(s?.uses ?? '').startsWith('actions/checkout'),
)
const sparse = String(checkout?.with?.['sparse-checkout'] ?? '')
.split('\n')
.map((path) => path.trim())
.filter(Boolean)
if (sparse.length > 0) expect(sparse).toContain(DOCKERFILE_DIR)The code finds the job that has a step whose The code uses three names. Check that the test defines them with these values:
If the test uses other names for these values, change the code to use those names.
Found by 1 model: codex
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in ee87669: |
||
| test -n "$ALPINE_NODE_IMAGE" | ||
| wrapper=$(basename "$(ls "$GITHUB_WORKSPACE"/auth-dist/cipherstash-auth-[0-9]*.tgz)") | ||
| musl=$(basename "$(ls "$GITHUB_WORKSPACE"/auth-dist/cipherstash-auth-linux-x64-musl-*.tgz)") | ||
| docker run --rm -v "$GITHUB_WORKSPACE/auth-dist:/dist:ro" \ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,7 +49,10 @@ jobs: | |
| with: | ||
| ref: ${{ inputs.ref }} | ||
| persist-credentials: false | ||
| sparse-checkout: scripts | ||
| # The musl smoke test reads the Dockerfile's FROM line. | ||
| sparse-checkout: | | ||
| scripts | ||
| .github/docker/musl-build | ||
|
|
||
| - uses: actions/download-artifact@v4 | ||
| with: | ||
|
|
@@ -152,12 +155,14 @@ jobs: | |
|
|
||
| # The host steps above install only linux-x64-gnu, the runner's own | ||
| # platform. The musl binary loads only where musl is the C library, so it | ||
| # is installed and loaded inside Alpine, from the image the build uses. | ||
| # is installed and loaded inside Alpine, on the base image the build | ||
| # image is made from: plain Node.js on musl, as a user's container is. | ||
| # Read from the Dockerfile so there is one digest for Dependabot to move. | ||
| - name: Smoke-test the musl artifact inside Alpine | ||
| env: | ||
| ALPINE_NODE_IMAGE: node:22-alpine@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402 | ||
| run: | | ||
| set -euo pipefail | ||
| ALPINE_NODE_IMAGE=$(sed -n 's/^FROM //p' .github/docker/musl-build/Dockerfile) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix before merge: The Impact: Every dispatched Evidence: The
Fix: Add the Dockerfile directory to the sparse checkout at line 52: sparse-checkout: |
scripts
.github/docker/musl-build
To stop the same mistake later, add a check to const job = Object.values(wf.jobs).find((j) =>
(j?.steps ?? []).some((s) => String(s?.run ?? '').includes(DOCKERFILE)),
)
const checkout = job.steps.find((s) =>
String(s?.uses ?? '').startsWith('actions/checkout'),
)
const sparse = String(checkout?.with?.['sparse-checkout'] ?? '')
.split('\n')
.map((path) => path.trim())
.filter(Boolean)
if (sparse.length > 0) expect(sparse).toContain(DOCKERFILE_DIR)The code finds the job that has a step whose The code uses three names. Check that the test defines them with these values:
If the test uses other names for these values, change the code to use those names. Found by 1 model: codex
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in ee87669, with auth-preflight. |
||
| test -n "$ALPINE_NODE_IMAGE" | ||
| wrapper=$(basename "$(ls "$GITHUB_WORKSPACE"/ffi-dist/cipherstash-protect-ffi-[0-9]*.tgz)") | ||
| musl=$(basename "$(ls "$GITHUB_WORKSPACE"/ffi-dist/cipherstash-protect-ffi-linux-x64-musl-*.tgz)") | ||
| docker run --rm -v "$GITHUB_WORKSPACE/ffi-dist:/dist:ro" \ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| name: musl build image | ||
|
|
||
| # The Dockerfile pins every apk package to an exact version, and Alpine drops a | ||
| # version from its index when it publishes the next one. Nothing else builds | ||
| # the image between releases, so without this a pin that stopped resolving | ||
| # would first be seen by a release. | ||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - ".github/docker/musl-build/**" | ||
| - ".github/workflows/musl-build-image.yml" | ||
| pull_request: | ||
| paths: | ||
| - ".github/docker/musl-build/**" | ||
| - ".github/workflows/musl-build-image.yml" | ||
| schedule: | ||
| - cron: "0 7 * * 1" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix in a follow-up: When the weekly run fails, GitHub notifies only one person. Impact: GitHub sends the failure notice for a Evidence: GitHub documentation, "Notifications for workflow runs": "Notifications for scheduled workflows are sent to the user who initially created the workflow. If a different user updates the cron syntax in the workflow file, subsequent notifications will be sent to that user instead." No scheduled workflow in Fix: Open an issue when the scheduled run fails. Give jobs:
build:
permissions:
contents: read
issues: write
steps:
# ... existing steps ...
- name: Open an issue when the weekly build fails
if: ${{ failure() && github.event_name == 'schedule' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
gh issue create --repo "$GITHUB_REPOSITORY" \
--title "musl build image no longer builds" \
--body "Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID. Re-pin with the command in .github/docker/musl-build/Dockerfile."Found by 1 model: claude
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Taken now rather than later, in ee87669: the job has |
||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build the Alpine musl image | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| # `issues: write` for the last step alone; see there. | ||
| permissions: | ||
| contents: read | ||
| issues: write | ||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
| with: | ||
| persist-credentials: false | ||
| # `--pull` fetches the digest the Dockerfile names rather than a cached | ||
| # tag, so the check builds what a release would. | ||
| - name: Build the image | ||
| run: docker build --pull -t cipherstash/musl-build:check .github/docker/musl-build | ||
| # The build steps rely on these being present; a Dockerfile that builds | ||
| # but lost one would fail minutes into a release build instead. | ||
| - name: Check the tools the build steps use | ||
| run: | | ||
| set -euo pipefail | ||
| docker run --rm cipherstash/musl-build:check sh -euc ' | ||
| echo "Alpine $(cat /etc/alpine-release)" | ||
| for tool in cc c++ cmake perl make curl git rustup-init node; do | ||
| command -v "$tool" >/dev/null || { echo "missing: $tool" >&2; exit 1; } | ||
| done | ||
| ' | ||
| # GitHub mails a scheduled run's failure to one person: whoever last | ||
| # edited the cron line. An issue is what the rest of the team sees. | ||
| - name: Open an issue when the weekly build fails | ||
| if: ${{ failure() && github.event_name == 'schedule' }} | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| gh issue create --repo "$GITHUB_REPOSITORY" \ | ||
| --title "The musl build image no longer builds" \ | ||
| --label "github-actions" \ | ||
| --body "The weekly build of .github/docker/musl-build/Dockerfile failed: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID. An apk pin has most likely stopped resolving; re-pin with the command in the Dockerfile's comment." | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional: No test checks the cooldown or the schedule of the new
dockerDependabot entry.Impact: The skill now says Dependabot opens 7-day cooldown PRs for
docker. If someone deletescooldown:or changes the schedule here, no test fails. Dependabot can then propose a new base image digest on the day it is published.Evidence:
e2e/tests/supply-chain.e2e.test.tschecks the cooldown only fornpmandgithub-actions.scripts/__tests__/musl-build-image.test.mjschecks only this entry'sdirectory. Every entry in.github/dependabot.ymlhasdefault-days: 7today, so a check on every entry passes on this branch.Fix: In
e2e/tests/supply-chain.e2e.test.ts, add this to thesupply chain — automated dependency updates (Dependabot)block:Then change "cooldown ≥ 3 days on npm/github-actions" on the "Test asserts" line in
skills/stash-supply-chain-security/SKILL.mdto "cooldown ≥ 3 days on every entry". To also keep the weekly schedule, extend the Dependabot test inmusl-build-image.test.mjs:Found by 2 models: claude, codex
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in ee87669: every-entry cooldown test in supply-chain.e2e.test.ts, the skill's "Test asserts" line updated, and the docker entry's weekly schedule asserted in musl-build-image.test.mjs.