Skip to content

ci: add a release dry run, so the release job can be executed without a tag - #214

Merged
shockalotti merged 1 commit into
mainfrom
ci/release-dry-run
Nov 3, 2026
Merged

shockalotti merged 1 commit into
mainfrom
ci/release-dry-run

Conversation

@shockalotti

Copy link
Copy Markdown
Contributor

The release job in docker-publish.yml is gated on refs/tags/v, so every step inside it runs for the first time on an actual release. That includes download-artifact, which is why merging the 4 → 8 bump (#209) left a change with no CI coverage at all: a PR run skips the job entirely.

That is a bad place to discover a breaking input rename.

Change

A workflow_dispatch trigger with a dry_run input, default true.

tag push branch push PR dry-run dispatch
build + push image yes yes no no
export/upload archives yes yes yes yes
release job runs yes no no yes
gh release create yes no no no

Concretely:

  • push: becomes ${{ github.event_name == 'push' }}, so a dispatch does not publish an image.
  • release.if becomes startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.dry_run).
  • Create GitHub Release gains if: startsWith(github.ref, 'refs/tags/v'), so a dry run stops before publishing.

How it is used

gh workflow run docker-publish.yml --ref main -f dry_run=true

That builds, packages, uploads, and runs download-artifact@v8 against the real artifact, then stops. It verifies #209 without a tag.

Verification

This PR's own CI exercises the build and upload path on pull_request unchanged. The dispatch is run on main immediately after merge, which is the first real execution of download-artifact@v8 and the thing #209 currently lacks coverage for.

… a tag

The release job is gated on `refs/tags/v`, so everything in it (packaging the
standalone archives, and especially `download-artifact`) runs for the first time
on a real release. That is how a dependency bump like download-artifact 4 to 8
can sit merged with no coverage until the next tag, where a failure blocks a
release.

Adds a `workflow_dispatch` trigger with a `dry_run` input, default true:

- build pushes nothing on a dispatch (`push` is now `event_name == 'push'`,
  so pushes and tags still publish and PRs and dispatches do not);
- the release job also runs on a dry-run dispatch, so its packaging and download
  steps execute against a real artifact;
- `Create GitHub Release` stays gated on a version tag, so a dry run cannot
  publish anything.

Rehearsing a release is now a manual dispatch, not a throwaway tag.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Important

The dry-run path can't go green as written: arm64-smoke still requires an image that a dispatch never pushes. The documented rehearsal also collides with the main push it is meant to follow.

Reviewed changes

  • New workflow_dispatch trigger — boolean dry_run input (default true) so the release job can be exercised without a tag.
  • Conditional image push — push: narrowed from github.event_name != 'pull_request' to github.event_name == 'push', so a dispatch builds without publishing while PR behaviour is unchanged.
  • Release job gating — release.if now also fires on a dry_run dispatch; Create GitHub Release stays gated on a version tag.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash | 𝕏

context: .
platforms: ${{ env.BUILD_PLATFORMS }}
push: ${{ github.event_name != 'pull_request' }}
push: ${{ github.event_name == 'push' }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

On a workflow_dispatch, push is false, but arm64-smoke still takes its "pull the image this build just published" path because every gate there tests github.event_name != 'pull_request' (lines 168, 180, 187), which is true for a dispatch. With nothing pushed, needs.build.outputs.digest is empty — buildx only emits containerimage.digest when a result is exported — so the step fails its explicit empty-digest check, and a non-empty digest would still docker pull an image that was never pushed. A dry run therefore always ends red, which is the opposite of the green rehearsal this PR adds.

Technical details
# Dry-run dispatch fails arm64-smoke

## Affected sites
- `.github/workflows/docker-publish.yml:100` — `push: ${{ github.event_name == 'push' }}` leaves a dispatch with no pushed image.
- `.github/workflows/docker-publish.yml:168` — `if: github.event_name != 'pull_request'` selects the pull path on dispatch.
- `.github/workflows/docker-publish.yml:173-176` — explicit `::error::build produced no digest` exit.
- `.github/workflows/docker-publish.yml:187-191` — `Resolve the image under test` chooses `calnode-pr:arm64` only for `pull_request`.

## Required outcome
- A `workflow_dispatch` run must not depend on an image this run pushed. Either skip `arm64-smoke` on dispatch, or let dispatch take the native-build path.
- If dispatch is routed through the native path, `Resolve the image under test` must select the locally built `calnode-pr:arm64` for dispatch too, not just `pull_request`.

# steps, then stop before publishing a GitHub Release. It exists so the release
# path can be executed without cutting a tag, which is otherwise the only way to
# find out whether it still works.
workflow_dispatch:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The trigger inherits concurrency: docker-publish-${{ github.ref }} with cancel-in-progress: true, and a dispatch on main — the usage documented in the PR body — lands in the same group as the merge commit's own main push build. Running the rehearsal right after merging therefore cancels that real :edge publish; a dispatch against a release tag cancels the tag build the same way.

Technical details
# Rehearsal cancels a real publish via shared concurrency group

## Affected sites
- `.github/workflows/docker-publish.yml:26` — new trigger joins an existing ref-keyed concurrency group.
- `.github/workflows/docker-publish.yml:33-35` — `group: docker-publish-${{ github.ref }}`, `cancel-in-progress: true`.

## Required outcome
- A dry-run dispatch must not cancel a concurrent push build for the same ref.

## Suggested approach
- Add the event (or the dry-run flag) to the group, e.g. `docker-publish-${{ github.ref }}-${{ github.event_name }}`, or set `cancel-in-progress` false for dispatch.

# Preinstalled gh CLI + GITHUB_TOKEN (no third-party action — keeps the
# supply-chain surface small).
- name: Create GitHub Release
if: startsWith(github.ref, 'refs/tags/v')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This guard checks only the ref, and a dispatch can target a tag (ref accepts a branch or tag name). On a tag ref startsWith(github.ref, 'refs/tags/v') is true, so gh release create runs even with dry_run: true, contradicting the input description, and would publish a release whose image this run never pushed. Require github.event_name == 'push' (or negate inputs.dry_run) in this condition.

@shockalotti
shockalotti merged commit e0db3a8 into main Nov 3, 2026
11 checks passed
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