|
| 1 | +# Shared GitHub Actions workflows |
| 2 | + |
| 3 | +Reusable workflows for all Aam-Digital repositories, so that each repository keeps only what is specific to it. |
| 4 | +Inputs, secrets and outputs are documented in the `workflow_call` section at the top of each file. |
| 5 | + |
| 6 | +| Workflow | What it does | Typical trigger in the caller | Permissions of the calling job | Check name | |
| 7 | +| --- | --- | --- | --- | --- | |
| 8 | +| [`docker-image.yml`](docker-image.yml) | Builds and pushes a container image (PR, edge, release), each platform on a native runner | `pull_request`, `push`, `release: published` | `contents: read`, `packages: write` | `<job> / build (<platform>)`, `<job> / merge` | |
| 9 | +| [`edge-version.yml`](edge-version.yml) | Computes the version of an edge build (`1.4.1-edge.5`) | `push` to the default branch | `contents: read` | `<job> / version` | |
| 10 | +| [`release.yml`](release.yml) | Creates the next full release with semantic-release | `push` to the default branch, or `workflow_dispatch` | `contents: read` (+ PAT as secret `token`) | `<job> / release` | |
| 11 | +| [`image-cleanup.yml`](image-cleanup.yml) | Deletes the `pr-<n>` image when a PR closes, or the edge images (`tags: "*-edge.*"`) after a full release; then untagged images older than 2 days | `pull_request: closed`, `release: published` | `actions: write`, `packages: write` | `<job> / cleanup` | |
| 12 | +| [`pr-title-lint.yml`](pr-title-lint.yml) | Checks that PR titles follow Conventional Commits | `pull_request_target` | `pull-requests: read` | `<job> / Validate PR title` | |
| 13 | +| [`actionlint.yml`](actionlint.yml) | Lints the repository's workflows (with shellcheck) | `pull_request`, `push` | `contents: read` | `<job> / actionlint` | |
| 14 | + |
| 15 | +`ci.yml` is this repository's own CI; it is not meant to be called. |
| 16 | + |
| 17 | +## Calling a workflow |
| 18 | + |
| 19 | +```yaml |
| 20 | +permissions: {} # nothing at the top level; each job grants what it needs |
| 21 | + |
| 22 | +jobs: |
| 23 | + build: |
| 24 | + uses: Aam-Digital/.github/.github/workflows/docker-image.yml@v1 |
| 25 | + permissions: |
| 26 | + contents: read |
| 27 | + packages: write |
| 28 | + with: |
| 29 | + images: ghcr.io/aam-digital/my-service |
| 30 | + tags: pr-${{ github.event.number }} |
| 31 | + platforms: linux/amd64 |
| 32 | +``` |
| 33 | +
|
| 34 | +- Always reference **`@v1`**, never a branch. |
| 35 | +- The calling job must grant the **permissions** from the table. A called workflow cannot get more, and the run fails |
| 36 | + before it starts. `permissions: {}` at the top level keeps `qlty check` (checkov) happy. |
| 37 | +- The caller's **secrets and `env` are not passed on**: pass secrets explicitly under `secrets:`, everything else as |
| 38 | + inputs. |
| 39 | +- **Check names** become `<caller job> / <called job>`. Moving an existing job into a shared workflow renames its |
| 40 | + check, so update the required checks of the ruleset when that PR merges. Skipped jobs (e.g. `merge` when `push` is |
| 41 | + false) count as passed. |
| 42 | +- `pr-title-lint.yml` only works with squash-only merging and the PR title as commit title. Call it on |
| 43 | + `pull_request_target` with the comment `# zizmor: ignore[dangerous-triggers]` on that line, otherwise `qlty check` |
| 44 | + fails. |
| 45 | +- PR images carry only their `pr-<n>` tag (otherwise the cleanup keeps them). PRs from forks build with `push: false`. |
| 46 | + |
| 47 | +Complete examples: [replication-backend](https://github.com/Aam-Digital/replication-backend/tree/master/.github/workflows) |
| 48 | +(auto release mode, PR images) and [ndb-core](https://github.com/Aam-Digital/ndb-core/tree/master/.github/workflows) |
| 49 | +(manual release mode, two registries, web app built in an earlier job and passed as an artifact). |
| 50 | + |
| 51 | +## Release model |
| 52 | + |
| 53 | +Every push to the default branch builds an **edge** image; full releases come from Conventional Commit PR titles via |
| 54 | +[semantic-release](https://semantic-release.gitbook.io/) (plugins in each repository's own `.releaserc*`). |
| 55 | + |
| 56 | +- **Edge build:** `edge-version.yml` computes `<last release, patch + 1>-edge.<commits since>`, e.g. `1.4.1-edge.5`. |
| 57 | + The image is tagged with it, but there is no git tag and no GitHub release. Move the `edge` image tag only if |
| 58 | + `is-head` is `true`, and give the image job a `concurrency` group with `cancel-in-progress: true`, so that an older |
| 59 | + run never moves `edge` back. |
| 60 | +- **Full release**, in one of two modes: **auto** (`release.yml` on every push; a `fix`/`feat`/breaking change is |
| 61 | + released right away) or **manual** (`release.yml` via `workflow_dispatch`). Releases are created with a PAT, because |
| 62 | + releases created with the `GITHUB_TOKEN` trigger no workflows. The `release: published` event builds the image, and |
| 63 | + `image-cleanup.yml` then deletes the edge images of the finished cycle. |
| 64 | + |
| 65 | +| Build | Image tags | |
| 66 | +| --- | --- | |
| 67 | +| push to the default branch | `1.4.1-edge.5`, `edge` | |
| 68 | +| full release `1.4.1` | `1.4.1`, `stable`, `latest` | |
| 69 | +| pull request #123 | `pr-123` | |
| 70 | + |
| 71 | +Pass `stable: ${{ !github.event.release.prerelease }}` for release images; never derive it from the version string. |
| 72 | + |
| 73 | +## Versioning (`v1`) |
| 74 | + |
| 75 | +There are no releases of this repository. After every push to `main`, CI moves `v1` to the new commit once actionlint |
| 76 | +has passed, so a merged PR (Renovate's included) reaches every repository on its next run. Merging here is the release |
| 77 | +step: check what a change does to the callers before merging. |
| 78 | + |
| 79 | +A change that breaks callers needs `v2`: set `MAJOR_TAG: v2` in `ci.yml` in the same PR, so that `v1` stays where it |
| 80 | +is, then switch the callers one by one. Breaking means: |
| 81 | + |
| 82 | +- removing or renaming an input, output or secret, or changing an input's default or meaning; |
| 83 | +- renaming a job id or job `name` (this changes check names); |
| 84 | +- requiring more permissions from the caller; |
| 85 | +- changing which image tags are produced for the same inputs. |
| 86 | + |
| 87 | +To test a change before merging, point a caller PR at the branch (`@<branch>`), and switch back to `@v1` before |
| 88 | +merging it. In an emergency, `v1` can be reset by hand: |
| 89 | +`gh api -X PATCH repos/Aam-Digital/.github/git/refs/tags/v1 -f sha=<good commit> -F force=true`. |
| 90 | + |
| 91 | +## Rules for the workflows here |
| 92 | + |
| 93 | +- Keep shell inline; never fetch scripts from this repository at runtime. |
| 94 | +- Pass inputs to `run:` scripts via `env:`, never by `${{ }}` interpolation into the script. |
| 95 | +- Actions of large trusted providers (`actions/*`, `docker/*`, `github/*`, …) use their major tag; other actions are |
| 96 | + pinned to a commit SHA with a `# vX.Y.Z` comment. Renovate (`renovate.json` in the repository root) updates both. |
| 97 | +- Steps shared between jobs of one workflow are written once and reused with YAML anchors (`&name` / `*name`). |
| 98 | +- Explain *why* in comments in the workflow file, next to the code; keep this README to what callers need. |
0 commit comments