Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9ef7dfe
ci: shared reusable workflows for image builds, PR image cleanup and …
sleidig Oct 13, 2026
ab5f526
feat(renovate): shared org preset
sleidig Oct 13, 2026
69fd96f
ci(pr-image-cleanup): cancel and wait for unfinished PR builds before…
sleidig Oct 13, 2026
bd95c45
docs(renovate): name the options that are concatenated, not replaced
sleidig Oct 13, 2026
31a778b
ci(pr-image-cleanup): serialize per PR instead of per package
sleidig Oct 13, 2026
8a6184f
Merge branch 'feat/renovate-preset' into feat/reusable-workflows
sleidig Oct 13, 2026
b62fb20
feat(docker-image): native-runner builds and deletion of replaced images
sleidig Oct 13, 2026
9d347ab
docs(workflows): zizmor ignore comment for the pr-title-lint caller's…
sleidig Oct 13, 2026
2ee376f
feat(release): shared semantic-release workflow for releases and edge…
sleidig Oct 13, 2026
8f88611
feat(image-prune): delete old prerelease images after a full release
sleidig Oct 13, 2026
3236da7
docs: org-wide default issue templates, SECURITY.md and CONTRIBUTING.md
sleidig Oct 13, 2026
8feee81
docs: document release, image-prune, native runners, replaced-image c…
sleidig Oct 13, 2026
749a528
docs(workflows): precise conditions for replaced-image deletion and p…
sleidig Oct 13, 2026
7e0da8f
fix(docker-image): avoid A && B || C for the shellcheck version on th…
sleidig Oct 13, 2026
1d5c2ae
fix(release,docker-image): skip release job off the default branch, s…
sleidig Oct 14, 2026
5d56cde
feat(docker-image): optional BUILD_SECRETS secret forwarded as BuildK…
sleidig Oct 14, 2026
b1974b7
simplify issue templates
sleidig Oct 16, 2026
b36aa45
refactor(docker-image): replace cleanup-replaced with an untagged swe…
sleidig Oct 16, 2026
594417c
ci: move v1 automatically after each merge and update the actions wit…
sleidig Oct 16, 2026
6482aa7
docs(workflows): merging is the release step, not necessarily a review
sleidig Oct 16, 2026
b68b4e3
docs: shorten the workflows README to what callers need
sleidig Oct 16, 2026
66db168
feat: edge builds without prereleases (edge-version.yml), release.yml…
sleidig Oct 16, 2026
2ce5d0a
chore(deps): update cycjimmy/semantic-release-action to v8.1.2
sleidig Oct 20, 2026
c581ac3
docs: Renovate presets README in the renovate folder; edge builds hav…
sleidig Oct 20, 2026
a011666
refactor(docker-image)!: native runners only, simpler registry login …
sleidig Oct 20, 2026
9f2ded8
refactor!: combine image-prune.yml and pr-image-cleanup.yml into imag…
sleidig Oct 20, 2026
59a775b
refactor(edge-version): always output a plain version, also after `v`…
sleidig Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: Bug report
about: Report a problem, something that does not work as expected
title: ""
labels: ""
type: "Bug"
assignees: ""
---

<!--
No personal data, credentials, instance names, hostnames or other client-specific details here.
Scrub logs and screenshots before adding them; link to internal sources (e.g. a Sentry issue) instead.
Security vulnerabilities: do not open an issue, see SECURITY.md (report them privately).
-->


What happens, and what you expected to happen instead.


## How to reproduce

<!-- Steps, request or configuration that triggers it.
Version (app version or image tag), relevant configuration, (scrubbed) logs or a link to the error in monitoring. -->

60 changes: 60 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
name: Feature request
about: Suggest a new functionality
title: ""
labels: ""
type: "New Feature"
assignees: ""
---

<!--
No personal data, credentials, instance names, hostnames or other client-specific details here.
Scrub logs and screenshots before adding them; link to internal sources (e.g. a Sentry issue) instead.
Keep this description short and readable for non-developers. Technical details go into the
implementation plan comment (see the guidelines at the end of this template).
-->

## What

<!-- The intended outcome, as observable behaviour: who can do what afterwards?
e.g. "As an administrator I want to ... in order to ..."
Also state what is explicitly out of scope. -->

## Why

<!-- The problem or use case behind it, and who is affected (users, administrators, operators). -->

## How

<!-- The proposed approach in a few sentences, and alternatives considered.
A small table or list helps to compare options. -->

---

Implementation plan: <!-- link to the plan comment, added once the objective is agreed -->

<!--
IMPLEMENTATION PLAN (not part of this description)

Once the objective above is agreed, post the implementation plan as ONE comment on this issue and
link it above. It is for the developer or agent implementing the change and must be
self-contained: someone who reads only that comment and the code can implement it without the
rest of the discussion. When decisions change, edit that comment in place instead of adding new
ones; change this description only if the objective changes.

## Implementation plan

### Context
The current state, verified against the code: file paths, how it behaves today. Anything assumed
but not verified is marked as such.

### Decisions
Each decision with its reason and the rejected alternative, including how risks are handled.

### Changes
Concrete steps: files, config keys, snippets where precision matters, then the tests and docs to
add or change.

### Open questions
What is still undecided and how it gets settled. Omit when there are none.
-->
20 changes: 20 additions & 0 deletions .github/ISSUE_TEMPLATE/technical_task.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: Technical task
about: A technical improvement, e.g. operations, observability, dependencies, CI or infrastructure
title: ""
labels: ""
type: "Task"
assignees: ""
---

<!--
No personal data, credentials, instance names, hostnames or other client-specific details here.
Scrub logs and screenshots before adding them; link to internal sources (e.g. a Sentry issue) instead.
Keep this description short and readable for reviewers. Technical details go into the
implementation plan comment (see the guidelines at the end of this template).
For pure refactorings without behaviour change, set the issue type to "Code Quality / Refactoring".
-->


- [ ] step 1
- [ ] step 2
98 changes: 98 additions & 0 deletions .github/workflows/README.md
Comment thread
sleidig marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Shared GitHub Actions workflows

Reusable workflows for all Aam-Digital repositories, so that each repository keeps only what is specific to it.
Inputs, secrets and outputs are documented in the `workflow_call` section at the top of each file.

| Workflow | What it does | Typical trigger in the caller | Permissions of the calling job | Check name |
| --- | --- | --- | --- | --- |
| [`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` |
| [`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` |
| [`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` |
| [`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` |
| [`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` |
| [`actionlint.yml`](actionlint.yml) | Lints the repository's workflows (with shellcheck) | `pull_request`, `push` | `contents: read` | `<job> / actionlint` |

`ci.yml` is this repository's own CI; it is not meant to be called.

## Calling a workflow

```yaml
permissions: {} # nothing at the top level; each job grants what it needs

jobs:
build:
uses: Aam-Digital/.github/.github/workflows/docker-image.yml@v1
permissions:
contents: read
packages: write
with:
images: ghcr.io/aam-digital/my-service
tags: pr-${{ github.event.number }}
platforms: linux/amd64
```

- Always reference **`@v1`**, never a branch.
- The calling job must grant the **permissions** from the table. A called workflow cannot get more, and the run fails
before it starts. `permissions: {}` at the top level keeps `qlty check` (checkov) happy.
- The caller's **secrets and `env` are not passed on**: pass secrets explicitly under `secrets:`, everything else as
inputs.
- **Check names** become `<caller job> / <called job>`. Moving an existing job into a shared workflow renames its
check, so update the required checks of the ruleset when that PR merges. Skipped jobs (e.g. `merge` when `push` is
false) count as passed.
- `pr-title-lint.yml` only works with squash-only merging and the PR title as commit title. Call it on
`pull_request_target` with the comment `# zizmor: ignore[dangerous-triggers]` on that line, otherwise `qlty check`
fails.
- PR images carry only their `pr-<n>` tag (otherwise the cleanup keeps them). PRs from forks build with `push: false`.

Complete examples: [replication-backend](https://github.com/Aam-Digital/replication-backend/tree/master/.github/workflows)
(auto release mode, PR images) and [ndb-core](https://github.com/Aam-Digital/ndb-core/tree/master/.github/workflows)
(manual release mode, two registries, web app built in an earlier job and passed as an artifact).

## Release model

Every push to the default branch builds an **edge** image; full releases come from Conventional Commit PR titles via
[semantic-release](https://semantic-release.gitbook.io/) (plugins in each repository's own `.releaserc*`).

- **Edge build:** `edge-version.yml` computes `<last release, patch + 1>-edge.<commits since>`, e.g. `1.4.1-edge.5`.
The image is tagged with it, but there is no git tag and no GitHub release. Move the `edge` image tag only if
`is-head` is `true`, and give the image job a `concurrency` group with `cancel-in-progress: true`, so that an older
run never moves `edge` back.
- **Full release**, in one of two modes: **auto** (`release.yml` on every push; a `fix`/`feat`/breaking change is
released right away) or **manual** (`release.yml` via `workflow_dispatch`). Releases are created with a PAT, because
releases created with the `GITHUB_TOKEN` trigger no workflows. The `release: published` event builds the image, and
`image-cleanup.yml` then deletes the edge images of the finished cycle.

| Build | Image tags |
| --- | --- |
| push to the default branch | `1.4.1-edge.5`, `edge` |
| full release `1.4.1` | `1.4.1`, `stable`, `latest` |
| pull request #123 | `pr-123` |

Pass `stable: ${{ !github.event.release.prerelease }}` for release images; never derive it from the version string.

## Versioning (`v1`)

There are no releases of this repository. After every push to `main`, CI moves `v1` to the new commit once actionlint
has passed, so a merged PR (Renovate's included) reaches every repository on its next run. Merging here is the release
step: check what a change does to the callers before merging.

A change that breaks callers needs `v2`: set `MAJOR_TAG: v2` in `ci.yml` in the same PR, so that `v1` stays where it
is, then switch the callers one by one. Breaking means:

- removing or renaming an input, output or secret, or changing an input's default or meaning;
- renaming a job id or job `name` (this changes check names);
- requiring more permissions from the caller;
- changing which image tags are produced for the same inputs.

To test a change before merging, point a caller PR at the branch (`@<branch>`), and switch back to `@v1` before
merging it. In an emergency, `v1` can be reset by hand:
`gh api -X PATCH repos/Aam-Digital/.github/git/refs/tags/v1 -f sha=<good commit> -F force=true`.

## Rules for the workflows here

- Keep shell inline; never fetch scripts from this repository at runtime.
- Pass inputs to `run:` scripts via `env:`, never by `${{ }}` interpolation into the script.
- Actions of large trusted providers (`actions/*`, `docker/*`, `github/*`, …) use their major tag; other actions are
pinned to a commit SHA with a `# vX.Y.Z` comment. Renovate (`renovate.json` in the repository root) updates both.
- Steps shared between jobs of one workflow are written once and reused with YAML anchors (`&name` / `*name`).
- Explain *why* in comments in the workflow file, next to the code; keep this README to what callers need.
20 changes: 20 additions & 0 deletions .github/workflows/actionlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Lints the calling repository's GitHub Actions workflows with actionlint
# (including shellcheck on `run:` scripts).
name: actionlint

on:
workflow_call:

jobs:
# The job id is part of the check name (`<caller job> / actionlint`). Do not rename it within v1.
actionlint:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false

- name: actionlint
uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# CI of this repository: lints the shared workflows themselves and, on `main`, moves the major tag that consumers
# reference (`@v1`) to the new commit. Every merge to `main` therefore reaches all repositories on their next run.
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

env:
# The major tag that follows `main`. For a breaking change (see README.md in .github/workflows), set this to the
# next major (`v2`) in the same PR: the old tag then stays where it is, and consumers switch one by one.
MAJOR_TAG: v1

jobs:
# Check name: `actionlint / actionlint` (required by the ruleset of `main`; do not rename)
actionlint:
uses: ./.github/workflows/actionlint.yml

move-major-tag:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: actionlint
runs-on: ubuntu-latest
permissions:
contents: write # move the tag
# Pushes are handled in order, so that an older run never moves the tag back.
concurrency:
group: move-major-tag
cancel-in-progress: false
steps:
- name: Move ${{ env.MAJOR_TAG }} to this commit
env:
GH_TOKEN: ${{ github.token }}
run: |
if current=$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$MAJOR_TAG" --jq .object.sha 2>/dev/null); then
status=$(gh api "repos/$GITHUB_REPOSITORY/compare/$current...$GITHUB_SHA" --jq .status)
case "$status" in
identical)
echo "$MAJOR_TAG already points to $GITHUB_SHA"
exit 0
;;
ahead) ;;
*)
echo "::warning::$GITHUB_SHA is not ahead of $MAJOR_TAG ($current, compare status '$status'), so the tag is not moved"
exit 0
;;
esac
gh api --silent -X PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/$MAJOR_TAG" -f sha="$GITHUB_SHA" -F force=true
echo "Moved $MAJOR_TAG from $current to $GITHUB_SHA"
else
gh api --silent -X POST "repos/$GITHUB_REPOSITORY/git/refs" -f ref="refs/tags/$MAJOR_TAG" -f sha="$GITHUB_SHA"
echo "Created $MAJOR_TAG at $GITHUB_SHA"
fi
Loading
Loading