From e993283b9e3f367d3e5c744b5483d8226596bdf8 Mon Sep 17 00:00:00 2001 From: bin101 Date: Fri, 10 Jul 2026 09:09:16 +0200 Subject: [PATCH] fix: run the Docker build in the same workflow run as release-please The v0.2.0 release cut a tag and GitHub Release with no GHCR image to show for it: publish.yml's `on: push: tags:` trigger never fired, because GitHub Actions doesn't let a ref created via the default GITHUB_TOKEN (which is how release-please-action creates its tag) trigger another workflow run -- a deliberate recursion guard, not a bug. Moves the Docker build into release-please.yml itself, as a `build` job gated on the release-please job's release_created output, so it runs in the same run that already has permission to act (no separate trigger needed). publish.yml becomes a workflow_dispatch-only fallback for re-publishing an existing tag on demand. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/publish.yml | 32 ++++++------- .github/workflows/release-please.yml | 69 +++++++++++++++++++++++++++- CLAUDE.md | 23 ++++++---- README.md | 9 +++- 4 files changed, 104 insertions(+), 29 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a8850af..e8e16c4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,14 +1,19 @@ -name: Build and publish Docker image +name: Manually rebuild and publish a release -# Fires only on a vX.Y.Z tag -- which only ever exists once release-please -# (see release-please.yml) has cut an actual release by merging its release -# PR. A plain push to main/develop never reaches this workflow at all, so -# the multi-arch build only ever runs for a real release. +# Manual fallback only. The normal path is the `build` job inside +# release-please.yml, which runs automatically in the same workflow run as +# the release -- see that file's header comment for why this can't instead +# be a plain `on: push: tags:` workflow (GITHUB_TOKEN-created tags don't +# trigger other workflow runs). Use this to re-publish an existing vX.Y.Z +# release on demand, e.g. after a GHCR outage or a manual retry. on: - push: - tags: ["v*.*.*"] workflow_dispatch: + inputs: + tag: + description: "Existing vX.Y.Z tag to rebuild and publish" + required: true + type: string permissions: contents: read @@ -26,6 +31,8 @@ jobs: packages: write steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} - name: Set up QEMU (for linux/arm64) uses: docker/setup-qemu-action@v3 @@ -40,23 +47,16 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # docker/metadata-action reads the semver straight off the triggering - # tag ref (github.ref), so no manual version wiring is needed here -- - # unlike the previous single-workflow setup where the build job had to - # check out a tag computed by an earlier job in the same run. - name: Extract image metadata (tags + OCI labels) id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{version}},value=${{ inputs.tag }} + type=semver,pattern={{major}}.{{minor}},value=${{ inputs.tag }} type=raw,value=latest - # Build always runs as a "does the Dockerfile still build (and pass - # its embedded test stage)?" check; push is unconditional here since - # this workflow itself only ever runs for a real vX.Y.Z tag. - name: Build and push image uses: docker/build-push-action@v6 with: diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index cb6399b..9afc607 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -4,8 +4,15 @@ name: Release Please # accumulates Conventional Commits into the next semantic version + # CHANGELOG.md entry. Nothing is tagged or released automatically -- only # merging that PR (a normal, reviewable PR merge, not a bot pushing straight -# to main) makes release-please cut the vX.Y.Z tag + GitHub Release. That -# tag push is what publish.yml reacts to. +# to main) makes release-please cut the vX.Y.Z tag + GitHub Release. +# +# The Docker build/publish job below runs in the SAME workflow run, +# triggered off release-please's own step outputs -- NOT off a separate +# `on: push: tags:` workflow. GitHub Actions deliberately does not let a ref +# (tag/branch) created using the default GITHUB_TOKEN trigger further +# workflow runs (recursion guard), and that's exactly how release-please +# creates its tag, so a second workflow listening for that tag push would +# never fire. Continuing in this same run sidesteps that entirely. on: push: @@ -15,11 +22,69 @@ permissions: contents: write pull-requests: write +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + jobs: release-please: runs-on: ubuntu-latest + outputs: + # release-please-action's output naming has changed across versions + # (bare vs. "--"-prefixed once a config/manifest file is used); + # the fallback covers whichever this action version actually emits. + release_created: ${{ steps.release.outputs.release_created || steps.release.outputs['.--release_created'] }} + tag_name: ${{ steps.release.outputs.tag_name || steps.release.outputs['.--tag_name'] }} steps: - uses: googleapis/release-please-action@v4 + id: release with: config-file: release-please-config.json manifest-file: .release-please-manifest.json + + build: + name: Build and push image + needs: release-please + if: needs.release-please.outputs.release_created == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.release-please.outputs.tag_name }} + + - name: Set up QEMU (for linux/arm64) + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract image metadata (tags + OCI labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=semver,pattern={{version}},value=${{ needs.release-please.outputs.tag_name }} + type=semver,pattern={{major}}.{{minor}},value=${{ needs.release-please.outputs.tag_name }} + type=raw,value=latest + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/CLAUDE.md b/CLAUDE.md index 6f3faed..d83d3f9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -107,17 +107,22 @@ the test stage, never test files themselves). `HEALTHCHECK` hits `/healthz` with ### Branching, versioning & release pipeline `develop` is the integration branch (feature branches → PR → `develop`); `main` only moves via a -PR from `develop`. Three separate workflows split the concerns: +PR from `develop`. - `.github/workflows/ci.yml` — the `test` job, on every push (`main`/`develop`) and every PR. -- `.github/workflows/release-please.yml` — runs - [Release Please](https://github.com/googleapis/release-please) (config: - `release-please-config.json` + `.release-please-manifest.json`) on every push to `main`. It never - pushes to `main` directly: it maintains a standing release PR (version bump + `CHANGELOG.md`) - from Conventional Commits (`fix:`, `feat:`, `BREAKING CHANGE:`) and only cuts the `vX.Y.Z` tag + - GitHub Release once that PR is actually merged. -- `.github/workflows/publish.yml` — triggers only on a `vX.Y.Z` tag push (i.e. only once - release-please has cut a release) and does the multi-arch Docker build + push to GHCR. +- `.github/workflows/release-please.yml` — runs on every push to `main`. Its `release-please` job + runs [Release Please](https://github.com/googleapis/release-please) (config: + `release-please-config.json` + `.release-please-manifest.json`), which never pushes to `main` + directly: it maintains a standing release PR (version bump + `CHANGELOG.md`) from Conventional + Commits (`fix:`, `feat:`, `BREAKING CHANGE:`) and only cuts the `vX.Y.Z` tag + GitHub Release once + that PR is merged. The workflow's `build` job then runs the multi-arch Docker build + GHCR push, + gated on `release-please`'s `release_created` output, **in that same run** — it is deliberately + *not* a separate `on: push: tags:` workflow, because GitHub Actions won't let a tag created via + the default `GITHUB_TOKEN` (which is how release-please creates it) trigger another workflow run; + a separate publish workflow listening for that tag push would simply never fire. (Learned this the + hard way: the first release-please rollout cut `v0.2.0` with no Docker image to show for it.) +- `.github/workflows/publish.yml` — manual-only (`workflow_dispatch`) fallback to rebuild/republish + an already-tagged release on demand. The version lives in `waypoint/__init__.py` (`__version__`, marked with a trailing `# x-release-please-version` comment that release-please's generic file updater matches on) — don't diff --git a/README.md b/README.md index 1005bec..1bfe84a 100644 --- a/README.md +++ b/README.md @@ -186,8 +186,13 @@ and the web routes (via Flask's test client) — all without needing a real IMAP keeps a standing **release PR** up to date with the accumulated version bump and `CHANGELOG.md` entry — it never pushes to `main` directly. - Merging that release PR (like any other PR, via review) is what actually cuts the `vX.Y.Z` tag + - GitHub Release. Only that tag push triggers `.github/workflows/publish.yml`, which builds the - multi-arch image and publishes it to GHCR — a plain commit landing on `main` never does. + GitHub Release — and, in the same workflow run, builds the multi-arch image and publishes it to + GHCR. (The build has to live in that same run rather than a separate `on: push: tags:` workflow: + GitHub Actions doesn't let a tag created via the default `GITHUB_TOKEN` — which is how + release-please creates it — trigger a second workflow run.) A plain commit landing on `main` + never builds or publishes anything. +- `.github/workflows/publish.yml` is a manual-only fallback (`workflow_dispatch`) for re-publishing + an existing tag on demand, e.g. after a registry outage. - Commit messages need a Conventional Commits prefix for Release Please to pick them up correctly: `fix:` → patch, `feat:` → minor, `BREAKING CHANGE:` (in the footer) → major. Anything else (`chore:`, `docs:`, `test:`, ...) is included in the changelog but doesn't bump the version.