Skip to content

feat: shared CI workflows, release automation, Renovate presets and org defaults - #1

Merged
sleidig merged 27 commits into
mainfrom
feat/reusable-workflows
Oct 20, 2026
Merged

sleidig merged 27 commits into
mainfrom
feat/reusable-workflows

Conversation

@sleidig

@sleidig sleidig commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Part of Aam-Digital/internal#392. This is the single consolidated PR for Aam-Digital/.github. It now also contains the Renovate presets from #2, which is closed in favour of this PR.

What

Reusable workflows (.github/workflows/, details in .github/workflows/README.md)

Workflow Purpose Check name(s) in the caller Caller permissions
docker-image.yml Builds and pushes images for PRs, edge builds and releases, each platform on a native runner <job> / build (linux/amd64), <job> / build (linux/arm64), <job> / merge contents: read, packages: write
edge-version.yml Version of an edge build (1.4.1-edge.5), and whether the commit is still the head <job> / version contents: read
release.yml semantic-release: full release <job> / release contents: read (writes use the token PAT)
image-cleanup.yml Deletes the pr-<n> image when the PR closes, or the edge images (tags: "*-edge.*") after a full release; then untagged images older than 2 days <job> / cleanup actions: write, packages: write
pr-title-lint.yml Conventional Commits PR titles <job> / Validate PR title pull-requests: read
actionlint.yml actionlint + shellcheck <job> / actionlint contents: read
  • docker-image.yml builds every platform on a native runner (ubuntu-latest / ubuntu-24.04-arm, no QEMU): one job build (<platform>) per platform pushes by digest, then a merge job creates the multi-arch image with all tags (skipped with push: false). It logs in to ghcr.io whenever it pushes, and to Docker Hub when DOCKERHUB_USERNAME is set. docker/metadata-action sets the org.opencontainers.image.version label from the first tag (the version, or e.g. pr-123).

    It deletes nothing itself. An image whose last tag moves to a newer push (an earlier pr-<n> push, the previous image of a branch tag like main) stays in the package untagged until the next run of image-cleanup.yml (below). Moving edge/latest/stable leaves the previous image tagged with its version.

    Steps shared between the jobs use YAML anchors.

  • edge-version.yml computes the version of an edge build from git: the last full release with the patch number bumped, plus the commits since (1.4.1-edge.5). Its output is-head says whether the commit is still the head of the default branch; only then does the caller add the edge tag. Inputs: tag-prefix (e.g. aam-backend-service/), identifier (default edge). The version is always plain, also after a v-prefixed release tag (all repos release without v now).

  • release.yml creates full releases only. It takes extra-plugins, node-version-file, the secrets token (required PAT) and slack-webhook, and has the outputs version and published. It overrides only branches (the caller's default branch); the plugin config stays in the repo.

  • image-cleanup.yml deletes the given tags (default pr-<n> of the closed PR; wildcards such as *-edge.* allowed, a bare * is rejected) with dataaxiom/ghcr-cleanup-action, after cancelling and awaiting unfinished PR builds (build-workflows). Images carrying one of exclude-tags (default edge,main,master,stable,latest) are neither deleted nor untagged. Then it deletes all untagged images of the package older than untagged-older-than (default 2 days, empty to skip) as a best-effort step, after PR closes and releases alike. ghcr-cleanup-action never deletes children of tagged images (reference-counted), and the age limit protects builds in progress. This replaces an earlier cleanup-replaced step in docker-image.yml (about 230 lines of bash) and the former separate image-prune.yml / pr-image-cleanup.yml.

Renovate presets (renovate/default, renovate/node, renovate/docker, from #2, unchanged; see renovate/README.md.)

Updates and versioning of this repo:

  • renovate.json extends renovate/default for the github-actions manager, so the actions inside the shared workflows (SHA pins and majors) get updated too.
  • ci.yml moves the v1 tag to every new commit on main once actionlint / actionlint passes (job move-major-tag; it creates v1 on the first run). There are no v1.x.y releases: a merged PR, Renovate's included, reaches every repository on its next run. A breaking change sets MAJOR_TAG: v2 in ci.yml in the same PR, so v1 stays where it is.

Org-wide community health defaults:

  • .github/ISSUE_TEMPLATE/: bug report, feature request, technical task. They are generalized from aam-services and set the org issue types (Bug, New Feature, Task) instead of repo-specific labels.
  • SECURITY.md and CONTRIBUTING.md, generalized from ndb-core. The wording is neutral, so it also fits private repos. There is no org PR template.

Docs:

  • The workflows README (short, for callers) covers the workflows with their triggers, permissions and check names, how to call them, the release model and how v1 moves. Inputs are documented in each workflow file.
  • The root README gives an overview; the Renovate presets are documented in renovate/README.md.
  • The pr-title-lint caller example now has a # zizmor: ignore[dangerous-triggers] comment with its reason (qlty's cloud defaults block on it otherwise).

Edge builds instead of prereleases

The issue planned semantic-release prereleases (branches: [{name: <default>, prerelease: edge}]). semantic-release rejects a lone prerelease branch (ERELEASEBRANCHES), and the workaround (a dry run plus tags and GitHub prereleases created by the workflow) was complex. Prereleases also filled the GitHub releases list: 22 of the first 30 entries of ndb-core's list are prereleases, and both the app's "latest changes" and ndb-setup's create-instance.sh only read that first page.

Instead, every push to the default branch is an edge build with no tag and no GitHub release:

  • The version comes from edge-version.yml (see above), e.g. 3.93.1-edge.39. It sorts above the last release and below the next one.
  • The image gets <version>, plus edge while the commit is the head. The caller's image job uses a concurrency group with cancel-in-progress: true, so an older run can't move edge back.
  • After each full release, image-cleanup.yml (tags: "*-edge.*") deletes the edge images of the finished cycle.
  • All four semantic-release repos (ndb-core, replication-backend, platform-admin-services, aam-services) build edge, also in auto release mode. ndb-core also publishes edge translations for that version.

Testing

  • actionlint 1.7.12 with shellcheck 0.11.0: clean on all workflows.
  • docker-image.yml, for real, in two runs of a temporary workflow building into the throwaway package ghcr.io/aam-digital/ci-test (package, caches and test branch deleted afterwards): the former QEMU mode, native runners (amd64 + arm64) → merge, and moving edge while the old image keeps its version tag. (These runs also tested the former cleanup-replaced step, which is removed now.)
  • The native-only docker-image.yml and the merged image-cleanup.yml in the consumer PRs' CI (ndb-core #4507, replication-backend #381, aam-services #261, platform-admin-services #54): every PR build (single platform) passed with build (linux/amd64) → merge. ndb-core's run logged in to Docker Hub and ghcr.io and created pr-4507 in both registries; on ghcr.io its org.opencontainers.image.version label is pr-4507 (from metadata-action), while replication-backend's own label override 0.0.0-pr.381 is kept. Consumer workflows linted with actionlint against the shared workflows resolved locally.
  • The release prune of image-cleanup.yml, for real (then still image-prune.yml), with *-test.* and exclude edge: the old prerelease image and its children were deleted. The image tagged edge + 0.0.2-test… kept both tags. The dry run deleted nothing.
  • edge-version.yml, its version step run read-only against the clones of ndb-core (3.93.1-edge.39, with the old -master.* tags ignored), replication-backend (1.15.6-edge.0), platform-admin-services (then v1.7.5-edge.0; the v is dropped now) and aam-services (2.3.2-edge.0 with tag-prefix: aam-backend-service/).
  • actionlint on each consumer with the shared workflows resolved locally (inputs, secrets and permissions of every call checked).
  • Not exercised:
    • the native-only docker-image.yml with two platforms (an arm64 leg plus merge of two digests, and for ndb-core two digests × two registries) since the rework: first exercised by each consumer's first edge build on its default branch, and by keycloak-aam's next release (private repo, arm64 runner);
    • release.yml (a real release) and edge-version.yml's is-head check (both only run on the default branch);
    • the untagged sweep in image-cleanup.yml (it only runs when a PR closes or after a release). Its child handling was checked in the action's source (image-deleter.ts: a shared child is only deleted with its last parent). Check the first PR-close run after merging.

After merging

See the rollout checklist in Aam-Digital/internal#392:

  1. Apply this repo's merge settings before merging (squash only, delete branch on merge), so feat/reusable-workflows is deleted on merge and no consumer keeps running it unnoticed.
  2. After the merge, check that CI's move-major-tag created v1, then set up the ruleset with actionlint / actionlint.
  3. Switch the consumer PRs from @feat/reusable-workflows to @v1 before merging them.

🤖 Generated with Claude Code

…linting

Adds docker-image.yml, pr-image-cleanup.yml, pr-title-lint.yml and
actionlint.yml as reusable workflows (consumed via @v1), plus CI that
runs actionlint on this repository's own workflows.

Refs Aam-Digital/internal#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig and others added 12 commits October 13, 2026 20:28
Add renovate/default (base for every repo), renovate/node and
renovate/docker add-on presets, derived from the configs the repos
already share, plus docs on extending them and how repo settings
merge with the presets.

Refs Aam-Digital/internal#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… deleting

The usual cause of leaked pr-<n> images is a PR build that is still
running when the PR is merged and pushes the tag after the cleanup.

Refs Aam-Digital/internal#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub keeps only one pending run per concurrency group, so a group per
package could cancel cleanups while one run waits for PR builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Combines the shared Renovate presets (formerly #2) into the
single consolidated PR for Aam-Digital/internal#392.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- native-runners: build each platform on a native runner (linux/amd64 on
  ubuntu-latest, linux/arm64 on ubuntu-24.04-arm), push by digest and merge
  the multi-platform image with all tags in a merge job
- cleanup-replaced (default on): after the push, delete the ghcr.io image a
  tag pointed to before if no tag is left on it, with its platform and
  attestation manifests unless another tagged image still uses them; in
  native mode also the merged per-platform pushes. Failures only warn.

Part of Aam-Digital/internal#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… pull_request_target

Part of Aam-Digital/internal#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… prereleases

type release: full release from the default branch, after deleting the
*-<prerelease-id>.* and *-master.* prereleases and tags. type prerelease:
<next version>-<prerelease-id>.<n>, computed by a semantic-release dry run
(semantic-release cannot use the default branch alone as prerelease branch)
and created as GitHub prerelease with the PAT.

Part of Aam-Digital/internal#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wildcard tag patterns (default *-edge.*,*-master.*) via ghcr-cleanup-action;
images that carry an excluded tag (edge, master, stable, latest) are never
deleted or untagged, untagged versions are never touched.

Also log "still tagged" before "still used" when docker-image keeps a
replaced image.

Part of Aam-Digital/internal#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generalised from the aam-services issue templates (org issue types instead of
labels) and ndb-core's security policy and contribution guide, in wording
that also fits private repositories.

Part of Aam-Digital/internal#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…leanup and the release model

Part of Aam-Digital/internal#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rerelease frequency

Part of Aam-Digital/internal#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sleidig sleidig changed the title ci: shared reusable workflows (docker image, PR image cleanup, PR title lint, actionlint) feat: shared CI workflows, release automation, Renovate presets and org defaults Oct 13, 2026
…e runners

Part of Aam-Digital/internal#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig and others added 2 commits October 14, 2026 07:54
…tatic 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>
…it secrets

Passed to build-push-action in the default and the native builds, masked in
the logs, a no-op when unset. Tested with a probe secret in both modes.

Part of Aam-Digital/internal#392

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comment thread .github/ISSUE_TEMPLATE/bug_report.md Resolved
Comment thread .github/ISSUE_TEMPLATE/bug_report.md Resolved
Co-authored-by: Sebastian <sebastian@aam-digital.com>
Comment thread .github/workflows/README.md Resolved
Comment thread README.md Resolved
sleidig and others added 2 commits October 16, 2026 16:20
…ep in pr-image-cleanup

docker-image.yml no longer deletes the images a push replaces (input `cleanup-replaced`, about 230 lines
of registry and package API bash). Instead, pr-image-cleanup.yml now also deletes the untagged images of
the package that are older than `untagged-older-than` (default 2 days), using ghcr-cleanup-action, which
never deletes children of tagged images. This covers earlier PR pushes, moving branch tags such as
`main`, the per-platform pushes of native-runner builds, and images left behind by overlapping pushes,
which the old step missed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h Renovate

- ci.yml moves the major tag (`MAJOR_TAG: v1`) to every new commit on main once actionlint passes, so
  there are no manual v1.x.y releases. For a breaking change, set MAJOR_TAG to v2 in the same PR.
- renovate.json extends the org preset for the github-actions manager, so the actions used by the
  shared workflows get updated and reach every repository through the moving v1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig added a commit to Aam-Digital/ndb-core that referenced this pull request Oct 16, 2026
The shared docker-image workflow no longer deletes replaced images itself; pr-image-cleanup now deletes
untagged images older than 2 days (Aam-Digital/.github#1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig added a commit to Aam-Digital/aam-services that referenced this pull request Oct 16, 2026
The shared docker-image workflow no longer deletes replaced images itself; pr-image-cleanup now deletes
untagged images older than 2 days (Aam-Digital/.github#1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig and others added 2 commits October 16, 2026 16:25
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The input, secret and output tables duplicated the workflow_call descriptions in each file, and the
rationale paragraphs duplicated the comments there. The README now has one table (purpose, trigger,
permissions, check name), one generic caller snippet with the rules that trip callers up, links to two
complete callers, and short sections on the release model and v1. The ndb-core-specific Renovate note
moves to ndb-core's renovate.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig added a commit to Aam-Digital/ndb-core that referenced this pull request Oct 16, 2026
Moved here from the shared preset's README (Aam-Digital/.github#1), since it only concerns this repo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… for full releases only

Every push to the default branch is built as an edge version: the last full release with the patch
number bumped, plus the commits since (1.4.1-edge.5). edge-version.yml computes it from git and says
whether the commit is still the head of the default branch (only then may the build move `edge`).
No semantic-release dry run, no tag, no GitHub release.

release.yml loses `type`, `prerelease-id` and `cleanup-prereleases` and only creates full releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig added a commit to Aam-Digital/ndb-core that referenced this pull request Oct 16, 2026
…prereleases

Uses the shared edge-version workflow (Aam-Digital/.github#1): version <last release, patch + 1>-edge.<commits
since>, tagged `edge` too while it is the newest commit. Edge images of a finished cycle are pruned after
the next full release. The shared release workflow no longer takes `type`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig added a commit to Aam-Digital/replication-backend that referenced this pull request Oct 16, 2026
…prereleases

Uses the shared edge-version workflow (Aam-Digital/.github#1): version <last release, patch + 1>-edge.<commits
since>, tagged `edge` too while it is the newest commit. Edge images of a finished cycle are pruned after
the next full release. The shared release workflow no longer takes `type`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig added a commit to Aam-Digital/platform-admin-services that referenced this pull request Oct 16, 2026
…prereleases

Uses the shared edge-version workflow (Aam-Digital/.github#1): version <last release, patch + 1>-edge.<commits
since>, tagged `edge` too while it is the newest commit. Edge images of a finished cycle are pruned after
the next full release. The shared release workflow no longer takes `type`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig added a commit to Aam-Digital/aam-services that referenced this pull request Oct 16, 2026
…prereleases

Uses the shared edge-version workflow (Aam-Digital/.github#1): version <last release, patch + 1>-edge.<commits
since>, tagged `edge` too while it is the newest commit. Edge images of a finished cycle are pruned after
the next full release. The shared release workflow no longer takes `type`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread .github/workflows/docker-image.yml Resolved
Comment thread .github/workflows/README.md Resolved
Comment thread .github/workflows/README.md Resolved
Comment thread README.md Resolved

@sleidig sleidig left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Seems good but let's check one more time if any actions can reuse code or be simplified in any way

sleidig and others added 4 commits October 20, 2026 09:07
…e no git tag (but an image tag)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and version label

- Every platform is built on a native runner (`build (<platform>)`, then `merge`); the QEMU mode and the
  `native-runners` input are removed. Check names are now `<job> / build (<platform>)` and `<job> / merge`.
- Log in to ghcr.io whenever pushing and to Docker Hub when DOCKERHUB_USERNAME is set, instead of parsing the
  registry hosts of the image names.
- The `org.opencontainers.image.version` label is left to docker/metadata-action (first tag = the version).
- No separate masking step for BUILD_SECRETS (the secrets passed in are already masked).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-cleanup.yml

One workflow deletes the `pr-<n>` image when a PR closes (default) or the given tag patterns (e.g. `*-edge.*` after a
release), then the untagged images older than 2 days, now also after releases. `exclude-tags` defaults to
`edge,main,master,stable,latest`. Callers grant `actions: write` and `packages: write`; check name `<job> / cleanup`.
(The two old files were already removed in the previous commit.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-prefixed release tags

All repositories now tag releases without `v`; a remaining `v` tag (e.g. the last release before a switch) no longer
leaks into the edge version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sleidig
sleidig merged commit e466249 into main Oct 20, 2026
2 checks passed
@sleidig
sleidig deleted the feat/reusable-workflows branch October 20, 2026 09:23
sleidig added a commit to Aam-Digital/ndb-core that referenced this pull request Oct 20, 2026
Aam-Digital/.github#1 is merged and tagged `v1`; its branch is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig added a commit to Aam-Digital/replication-backend that referenced this pull request Oct 20, 2026
Aam-Digital/.github#1 is merged and tagged `v1`; its branch is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig added a commit to Aam-Digital/aam-services that referenced this pull request Oct 20, 2026
Aam-Digital/.github#1 is merged and tagged `v1`; its branch is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sleidig added a commit to Aam-Digital/platform-admin-services that referenced this pull request Oct 20, 2026
Aam-Digital/.github#1 is merged and tagged `v1`; its branch is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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