Skip to content

Commit 1d5c2ae

Browse files
sleidigclaude
andcommitted
fix(release,docker-image): skip release job off the default branch, static native job name, permissions in examples
- release.yml: job-level if on the default branch, concurrency group per ref and type, so a no-op run of another branch cannot replace a pending release - docker-image.yml: static job name build-native; skipped jobs no longer show the unexpanded matrix expression, native runs keep build-native (<platform>) - README: all caller examples set top-level permissions: {} (checkov CKV2_GHA_1) Part of Aam-Digital/internal#392 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent 7e0da8f commit 1d5c2ae

4 files changed

Lines changed: 42 additions & 11 deletions

File tree

‎.github/workflows/README.md‎

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,19 @@ Reusable workflows for all Aam-Digital repositories, so that each repository kee
2121
This way a push to this repository (e.g. an edit of the org profile README) does not change CI everywhere.
2222
- **Permissions:** a called workflow cannot get more permissions than the calling job has.
2323
The calling job (or the caller workflow's top-level `permissions`) must grant at least what is listed for each
24-
workflow below, otherwise the run fails before it starts.
24+
workflow below, otherwise the run fails before it starts. Callers should set `permissions: {}` at the top level of
25+
the workflow and grant the permissions per job (as in all examples below); without a top-level `permissions`,
26+
checkov (`CKV2_GHA_1`, blocking in `qlty check`) flags the workflow. Jobs of the caller that are not shown in an
27+
example (tests, ...) need their own `permissions`, e.g. `contents: read`.
2528
- **Secrets and `env`** of the caller are not passed through. Everything a workflow needs is an input or an explicitly
2629
passed secret.
2730
- **Check names:** GitHub names the check of a job in a called workflow `<caller job> / <called job>`, where each part is
2831
the job's `name` if it has one, else its id. The called job ids/names listed above are part of the `v1` contract.
2932
If you move an existing job into one of these workflows, either keep the caller job's name so that the check name is
3033
what the repository's ruleset expects, or update the required status checks in the same PR. Otherwise PRs wait
31-
forever for a check that never reports. Jobs of the mode that is not used (e.g. `merge` without `native-runners`)
32-
report as skipped; a skipped required check counts as passed.
34+
forever for a check that never reports. Jobs of the mode that is not used (e.g. `merge` and `build-native` without
35+
`native-runners`) report as skipped (`<caller job> / build-native`, without the platform); a skipped required check
36+
counts as passed.
3337

3438
## Release model
3539

@@ -124,6 +128,8 @@ needed). `pr-image-cleanup.yml` never deletes an image that carries another tag.
124128
on:
125129
pull_request:
126130

131+
permissions: {} # each job grants what it needs
132+
127133
jobs:
128134
build:
129135
uses: Aam-Digital/.github/.github/workflows/docker-image.yml@v1
@@ -144,6 +150,8 @@ on:
144150
release:
145151
types: [published] # the release must be created with a PAT, GITHUB_TOKEN releases trigger no workflows
146152

153+
permissions: {} # each job grants what it needs
154+
147155
jobs:
148156
build:
149157
uses: Aam-Digital/.github/.github/workflows/docker-image.yml@v1
@@ -170,6 +178,8 @@ jobs:
170178
### Example: several registries, artifact from an earlier job
171179
172180
```yaml
181+
permissions: {} # each job grants what it needs
182+
173183
jobs:
174184
build-webapp:
175185
# ... uploads the artifact "webapp-files"
@@ -201,8 +211,10 @@ Creates the next release or prerelease with semantic-release
201211
([`cycjimmy/semantic-release-action`](https://github.com/cycjimmy/semantic-release-action)). Only the semantic-release
202212
`branches` setting comes from this workflow (the caller's default branch); the plugins and everything else stay in the
203213
repository's own configuration (`.releaserc*`, `release.config.*` or `release` in package.json), including `tagFormat`
204-
(which must end with the version). Releases are only created from the default branch; on any other ref the job does
205-
nothing (`published: false`).
214+
(which must end with the version). Releases are only created from the default branch: on any other ref the job is
215+
**skipped** by the workflow itself (no gate needed in the caller; the outputs are empty then). Skipping, rather than
216+
a no-op run, matters: GitHub keeps only one *pending* run per concurrency group, so a no-op run of a push to another
217+
branch could otherwise replace a release that is waiting. The concurrency group is per repository, ref and type.
206218

207219
- `type: release`: a full release of the default branch (`branches: [{name: <default>, prerelease: false}]`).
208220
With `cleanup-prereleases` (default), the GitHub prereleases and git tags matching `*-<prerelease-id>.*` or
@@ -234,7 +246,7 @@ write access). Releases of one repository and type are serialised (concurrency g
234246
| `token` | (required) PAT for tags, releases (and comments). Not the `GITHUB_TOKEN`. |
235247
| `slack-webhook` | Optional, available to plugins as `SLACK_WEBHOOK` (e.g. `semantic-release-slack-bot`) |
236248

237-
Outputs: `version` (the released version, empty if none), `published` (`true`/`false`).
249+
Outputs: `version` (the released version, empty if none), `published` (`true`/`false`; empty if the job was skipped on a non-default branch).
238250

239251
### Example: manual release mode
240252

@@ -244,6 +256,8 @@ on:
244256
push:
245257
branches: [main]
246258
259+
permissions: {} # each job grants what it needs
260+
247261
jobs:
248262
# ... tests
249263
prerelease:
@@ -262,6 +276,8 @@ jobs:
262276
on:
263277
workflow_dispatch:
264278
279+
permissions: {} # each job grants what it needs
280+
265281
jobs:
266282
release:
267283
uses: Aam-Digital/.github/.github/workflows/release.yml@v1
@@ -283,6 +299,8 @@ on:
283299
push:
284300
branches: [main]
285301
302+
permissions: {} # each job grants what it needs
303+
286304
jobs:
287305
# ... tests
288306
release:
@@ -350,6 +368,8 @@ on:
350368
pull_request:
351369
types: [closed]
352370
371+
permissions: {} # each job grants what it needs
372+
353373
jobs:
354374
cleanup-image:
355375
# PRs from forks have no image (and their token cannot delete packages)
@@ -383,6 +403,8 @@ on:
383403
pull_request_target: # zizmor: ignore[dangerous-triggers] never checks out PR code, only reads the PR title from the event payload
384404
types: [opened, edited, synchronize, reopened]
385405

406+
permissions: {} # each job grants what it needs
407+
386408
jobs:
387409
pr-title:
388410
uses: Aam-Digital/.github/.github/workflows/pr-title-lint.yml@v1
@@ -404,6 +426,8 @@ on:
404426
push:
405427
branches: [main]
406428
429+
permissions: {} # each job grants what it needs
430+
407431
jobs:
408432
actionlint:
409433
uses: Aam-Digital/.github/.github/workflows/actionlint.yml@v1

‎.github/workflows/docker-image.yml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,9 @@ jobs:
535535
# pushes its image by digest only. Do not rename within v1.
536536
build-native:
537537
if: inputs.native-runners
538-
name: build-native (${{ matrix.platform }})
538+
# No `name` with a matrix expression: GitHub appends the matrix value itself (`build-native (linux/amd64)`),
539+
# while the jobs skipped without native-runners would show the unexpanded expression.
540+
name: build-native
539541
strategy:
540542
fail-fast: false
541543
matrix:

‎.github/workflows/release.yml‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,24 @@ on:
6161
description: The version that was released (empty if none)
6262
value: ${{ jobs.release.outputs.version }}
6363
published:
64-
description: "`true` if a release or prerelease was created, else `false`"
64+
description: "`true` if a release or prerelease was created, `false` if there was nothing to release (empty if the job was skipped because the ref is not the default branch)"
6565
value: ${{ jobs.release.outputs.published }}
6666

6767
jobs:
6868
# The job id is part of the check name (`<caller job> / release`). Do not rename it within v1.
6969
release:
70+
# Only the default branch is released. Skipping the job on any other ref (instead of a no-op run inside it)
71+
# also keeps it out of the concurrency group below: GitHub keeps just one *pending* run per group, so a no-op run
72+
# of a push to another branch would otherwise replace a release that is waiting for the default branch.
73+
# (`published` is empty, not `false`, when the job is skipped.)
74+
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
7075
runs-on: ubuntu-latest
7176
permissions:
7277
contents: read # everything that writes uses the `token` secret
7378
# Releases of one repository must not run in parallel (they would compute the same version).
74-
# Separate groups per type, so that a prerelease of a later push never replaces a pending manual release.
79+
# Separate groups per type and ref, so that a prerelease of a later push never replaces a pending manual release.
7580
concurrency:
76-
group: semantic-release-${{ github.repository }}-${{ inputs.type }}
81+
group: semantic-release-${{ github.repository }}-${{ github.ref }}-${{ inputs.type }}
7782
cancel-in-progress: false
7883
outputs:
7984
version: ${{ steps.result.outputs.version }}

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Shared configuration for the repositories of the Aam-Digital organisation.
55

66
| Folder / file | What |
77
| --- | --- |
8-
| [`.github/workflows/`](.github/workflows) | Reusable GitHub Actions workflows (container images, releases, image cleanup, PR title and workflow linting). See its [README](.github/workflows/README.md) for inputs, permissions, check names, the release model and how the `v1` tag is moved. |
8+
| [`.github/workflows/`](.github/workflows) | Reusable GitHub Actions workflows (container images, releases, image cleanup, PR title and workflow linting). See its [README](.github/workflows/README.md) for inputs, permissions, check names, the release model and how the `v1` tag is moved. Callers set top-level `permissions: {}` and grant permissions per job. |
99
| [`renovate/`](renovate) | Shared Renovate presets (below) |
1010
| [`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE), [`SECURITY.md`](SECURITY.md), [`CONTRIBUTING.md`](CONTRIBUTING.md) | Organisation-wide defaults (below) |
1111

0 commit comments

Comments
 (0)