diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 7a341afc..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -name: Bug report -about: Report a problem with the backend services -title: "" -labels: "" -type: "Bug" -assignees: "" ---- - - - -## What - - - -## Why - - - -## How to reproduce - - - ---- - -Implementation plan: - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 084c852a..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -name: Feature request -about: Suggest a new functionality of the backend services -title: "" -labels: "" -type: "New Feature" -assignees: "" ---- - - - -## What - - - -## Why - - - -## How - - - ---- - -Implementation plan: - - diff --git a/.github/ISSUE_TEMPLATE/technical_task.md b/.github/ISSUE_TEMPLATE/technical_task.md deleted file mode 100644 index cc027c73..00000000 --- a/.github/ISSUE_TEMPLATE/technical_task.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -name: Technical task -about: A technical improvement, e.g. operations, observability, dependencies or infrastructure -title: "" -labels: "" -type: "Task" -assignees: "" ---- - - - -## What - - - -## Why - - - -## How - - - ---- - -Implementation plan: - - diff --git a/.github/workflows/_module-release.yml b/.github/workflows/_module-release.yml new file mode 100644 index 00000000..e531b947 --- /dev/null +++ b/.github/workflows/_module-release.yml @@ -0,0 +1,148 @@ +# Creates the next release of one module of this monorepo, e.g. `aam-backend-service/2.3.0`. +# +# Called by the module's build-and-publish workflow on pushes to main. The next version is calculated with +# conventional_commits_next_version from the commits since the module's last release that touch +# `application/`. Without such commits, or if they do not call for a new version (e.g. only `chore`), +# nothing is released. +# +# The release is created with the `token` PAT: a tag created with the GITHUB_TOKEN would not trigger the tag-push +# run of the module's workflow, which builds the release image. +name: module-release + +on: + workflow_call: + inputs: + module: + type: string + required: true + description: >- + Module name: the folder under `application/` and the tag prefix of its releases + (`/`), e.g. `aam-backend-service`. + version-prefix: + type: string + required: false + default: "" + description: >- + Prefix of the version in the release tag. `v` for modules whose consumers expect `/v`. + Existing tags are found with or without `v` either way. + asset-artifact: + type: string + required: false + default: "" + description: Name of a workflow artifact of this run whose files are attached to the release + secrets: + token: + required: true + description: PAT that can create tags and releases (not the GITHUB_TOKEN, see above) + outputs: + version: + description: The released version (without prefix), empty if nothing was released + value: ${{ jobs.release.outputs.version }} + +permissions: {} + +jobs: + # The job id is part of the check name (` / release`). + release: + runs-on: ubuntu-latest + # Serializes releases of a module across overlapping main-branch pushes (e.g. several PRs merged within + # minutes of each other). Without this, two runs can both read the same last-released tag before either + # creates the next one and race to create the same release. + concurrency: + group: module-release-${{ inputs.module }} + cancel-in-progress: false + permissions: + contents: read # everything that writes uses the `token` PAT + outputs: + version: ${{ steps.release.outputs.version }} + defaults: + run: + working-directory: application/${{ inputs.module }} + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + fetch-tags: true + persist-credentials: false + + - name: Download release assets + if: inputs.asset-artifact != '' + uses: actions/download-artifact@v8 + with: + name: ${{ inputs.asset-artifact }} + path: ${{ runner.temp }}/release-assets + + - name: Install Rust toolchain + run: rustup update --no-self-update + + # Keep this on one line: Renovate's custom regex manager (renovate.json) updates the version here. + - name: Install conventional_commits_next_version + run: cargo install conventional_commits_next_version --version ^7.0.0 + + - name: Calculate next version + id: next + env: + MODULE: ${{ inputs.module }} + run: | + # Release tags are `/` or `/v`. + if ! current_tag=$(git describe --abbrev=0 --tags --match "$MODULE/[0-9]*" --match "$MODULE/v[0-9]*"); then + echo "::error::No release tag of $MODULE found" + exit 1 + fi + current_version=${current_tag#"$MODULE/"} + current_version=${current_version#v} + from_commit=$(git rev-list -n 1 "$current_tag") + echo "Last release: $current_tag ($from_commit)" + + # conventional_commits_next_version (>= 7.1) fails if no commit in the range touches the module, + # so keep the current version (= no release) in that case. + if [ -z "$(git rev-list --first-parent --max-count=1 "$from_commit"..HEAD -- ":(top)application/$MODULE")" ]; then + echo "No commits that touch application/$MODULE since $current_tag" + next_version=$current_version + else + next_version=$(conventional_commits_next_version \ + --calculation-mode "Batch" \ + --from-version "$current_version" \ + --monorepo "application/$MODULE" \ + "$from_commit") + fi + echo "Next version: $next_version" + { + echo "current-tag=$current_tag" + echo "current-version=$current_version" + echo "next-version=$next_version" + } >> "$GITHUB_OUTPUT" + + - name: Create release + id: release + if: steps.next.outputs.next-version != steps.next.outputs.current-version + env: + GH_TOKEN: ${{ secrets.token }} + MODULE: ${{ inputs.module }} + VERSION_PREFIX: ${{ inputs.version-prefix }} + CURRENT_TAG: ${{ steps.next.outputs.current-tag }} + NEXT_VERSION: ${{ steps.next.outputs.next-version }} + ASSET_DIR: ${{ inputs.asset-artifact != '' && format('{0}/release-assets', runner.temp) || '' }} + run: | + if ! [[ "$NEXT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Unexpected next version '$NEXT_VERSION'" + exit 1 + fi + tag="$MODULE/$VERSION_PREFIX$NEXT_VERSION" + assets=() + if [ -n "$ASSET_DIR" ]; then + mapfile -t assets < <(find "$ASSET_DIR" -maxdepth 1 -type f | sort) + if [ "${#assets[@]}" -eq 0 ]; then + echo "::error::No release assets found in the artifact" + exit 1 + fi + fi + # With assets, gh creates a draft, uploads them and only then publishes the release (and its tag). + gh release create "$tag" "${assets[@]}" \ + --repo "$GITHUB_REPOSITORY" \ + --target "$GITHUB_SHA" \ + --title "$tag" \ + --generate-notes \ + --notes-start-tag "$CURRENT_TAG" + echo "version=$NEXT_VERSION" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/aam-backend-service-build-and-publish.yml b/.github/workflows/aam-backend-service-build-and-publish.yml index 335a3a12..a7c98b24 100644 --- a/.github/workflows/aam-backend-service-build-and-publish.yml +++ b/.github/workflows/aam-backend-service-build-and-publish.yml @@ -4,6 +4,7 @@ on: pull_request: paths: - '.github/workflows/aam-backend-service-build-and-publish.yml' + - '.github/workflows/_module-release.yml' - 'application/aam-backend-service/**' push: tags: @@ -12,9 +13,8 @@ on: - main paths: - '.github/workflows/aam-backend-service-build-and-publish.yml' + - '.github/workflows/_module-release.yml' - 'application/aam-backend-service/**' -env: - REGISTRY_IMAGE: ghcr.io/aam-digital/aam-services permissions: contents: read @@ -68,254 +68,139 @@ jobs: # (com/aamdigital/...), so prefix them to make them repo-relative. add-prefix: application/aam-backend-service/src/main/kotlin - build: - strategy: - fail-fast: false - matrix: - include: - - platform: linux/amd64 - os: ubuntu-latest - - platform: linux/arm64 - os: ubuntu-24.04-arm - runs-on: ${{ matrix.os }} + # Version of the edge build of a push to main (see Aam-Digital/.github): `X.Y.Z-edge.N`, the last + # `aam-backend-service/X.Y.Z` release with the patch number bumped, plus the commits since. No tag, no GitHub release. + # Runs for every trigger (it is cheap), so that the jobs below never depend on a skipped job; only main uses it. + edge-version: + uses: Aam-Digital/.github/.github/workflows/edge-version.yml@v1 permissions: contents: read - packages: write + with: + tag-prefix: aam-backend-service/ + + # Tags and build settings of the image, by trigger: + # - release tag `aam-backend-service/` (created by the `publish` job): ``, `stable`, `latest` + # - push to main (edge build): `X.Y.Z-edge.N`, plus `edge` and `main` (until nothing follows `main` any more) if it is + # still the newest main commit + # - pull request: `pr-`, linux/amd64 only + image-config: + needs: edge-version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.config.outputs.version }} + stable: ${{ steps.config.outputs.stable }} + tags: ${{ steps.config.outputs.tags }} + platforms: ${{ steps.config.outputs.platforms }} + application-version: ${{ steps.config.outputs.application-version }} steps: - - name: Checkout repository files - uses: actions/checkout@v7 - - - name: Extract tag (release) - if: ${{ github.event.ref != '' }} - run: echo "TAG=${GITHUB_REF#refs/tags/aam-backend-service/}" >> $GITHUB_ENV - - - name: Extract tag (pull request) - if: ${{ github.event.ref == '' }} - run: echo "TAG=pr-${{ github.event.number }}" >> $GITHUB_ENV - - - name: Determine application version - # Baked into the image as sentry.release (see application.yaml) so error events are - # tagged with a version. Tag pushes get the released version; everything else (main, - # PRs) gets the commit SHA, since TAG above is not unique per build in those cases. - run: | - if [[ "$GITHUB_REF" == refs/tags/aam-backend-service/* ]]; then - echo "APPLICATION_VERSION=${GITHUB_REF#refs/tags/aam-backend-service/}" >> $GITHUB_ENV - else - echo "APPLICATION_VERSION=${{ github.sha }}" >> $GITHUB_ENV - fi - - - name: Prepare Platform - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: Docker meta - id: meta - uses: docker/metadata-action@v6 - with: - tags: | - type=ref,enable=true,event=pr - type=ref,enable=true,pattern={{raw}},priority=600,value=${{ env.TAG }},event=branch - # {{version}} rather than {{raw}}: plain versions (e.g. `1.22.14`) like those of - # ndb-core, also for a rebuild of an older `v`-prefixed tag. - type=semver,enable=true,pattern={{version}},priority=600,value=${{ env.TAG }},event=tag - # Moving tag for the latest release, like ndb-core's; `latest` is added - # automatically for semver tags. - type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/') }} - images: ${{ env.REGISTRY_IMAGE }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Create export directories - run: | - mkdir -p ./tmp/build/${{ env.PLATFORM_PAIR }} - mkdir -p ./tmp/digests - - - name: Build Image and push by digest - id: build - uses: docker/build-push-action@v7 - with: - platforms: ${{ matrix.platform }} - context: ./application/aam-backend-service - provenance: false - labels: ${{ steps.meta.outputs.labels }} - # SENTRY_UPLOAD_SOURCES lets the Gradle build inside the image upload this build's - # source bundle, so Sentry can show our source code next to the stack trace frames - # (see Dockerfile). Off for pull requests: those images are never deployed, and forks - # cannot read the secret anyway. - build-args: | - APPLICATION_VERSION=${{ env.APPLICATION_VERSION }} - SENTRY_UPLOAD_SOURCES=${{ github.event_name != 'pull_request' }} - # Empty input rather than an empty value: `sentry_auth_token=` alone makes the action - # warn that it is not a valid secret. Keep this condition in sync with the one on - # SENTRY_UPLOAD_SOURCES above: a build that is told to upload but gets no token fails - # on purpose, rather than quietly skipping the upload. - secrets: ${{ github.event_name != 'pull_request' && format('sentry_auth_token={0}', secrets.SENTRY_TOKEN) || '' }} - outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true - cache-from: type=gha,url=${{ runner.env.ACTIONS_CACHE_URL }},token=${{ runner.env.ACTIONS_RUNTIME_TOKEN }} - cache-to: type=gha,mode=min,url=${{ runner.env.ACTIONS_CACHE_URL }},token=${{ runner.env.ACTIONS_RUNTIME_TOKEN }} + - name: Determine image tags and platforms + id: config env: - SOURCE_DATE_EPOCH: 0 - - - name: Export digest + PR_NUMBER: ${{ github.event.pull_request.number }} + EDGE_VERSION: ${{ needs.edge-version.outputs.version }} + EDGE_IS_HEAD: ${{ needs.edge-version.outputs.is-head }} run: | - digest="${{ steps.build.outputs.digest }}" - touch "./tmp/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v7 - with: - name: digests-${{ env.PLATFORM_PAIR }} - path: ./tmp/digests/* - if-no-files-found: error - retention-days: 1 - - merge: - runs-on: ubuntu-latest + version="" + stable=false + tags="" + platforms=linux/amd64,linux/arm64 + # Baked into the image as sentry.release (see application.yaml) so error events are tagged with a + # version: the released or edge version, else (PRs) the commit SHA (unique per build). + application_version=$GITHUB_SHA + case "$GITHUB_REF" in + refs/tags/aam-backend-service/*) + # `{{version}}`-style plain versions (e.g. `1.22.14`) like those of ndb-core, also for a rebuild + # of an older `v`-prefixed tag + version=${GITHUB_REF#refs/tags/aam-backend-service/} + version=${version#v} + stable=true + application_version=$version + ;; + refs/heads/main) + version=$EDGE_VERSION + application_version=$EDGE_VERSION + # an older run that finishes late must not move the moving tags back + [ "$EDGE_IS_HEAD" != "true" ] || tags=edge,main + ;; + *) + if [ -z "$PR_NUMBER" ]; then + echo "::error::Unexpected trigger $GITHUB_EVENT_NAME on $GITHUB_REF" + exit 1 + fi + tags=pr-$PR_NUMBER + # PR builds stay basic; release and main images are built for both platforms. + platforms=linux/amd64 + ;; + esac + { + echo "version=$version" + echo "stable=$stable" + echo "tags=$tags" + echo "platforms=$platforms" + echo "application-version=$application_version" + } >> "$GITHUB_OUTPUT" + echo "version=$version stable=$stable tags=$tags platforms=$platforms application-version=$application_version" + + # Check names: `build / build (linux/amd64)`, `build / build (linux/arm64)` (not on PRs), `build / merge` + build: + needs: image-config + # On main, a newer push cancels an older edge build, so that `edge` never moves back. Other builds get a group of + # their own (no cancelling). + concurrency: + group: ${{ github.ref == 'refs/heads/main' && 'edge-build' || format('build-{0}', github.run_id) }} + cancel-in-progress: true + uses: Aam-Digital/.github/.github/workflows/docker-image.yml@v1 permissions: contents: read packages: write - needs: - - build - steps: - - name: Download digests - uses: actions/download-artifact@v8 - with: - path: ./tmp/digests - pattern: digests-* - merge-multiple: true - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Extract tag (release) - if: ${{ github.event.ref != '' }} - run: echo "TAG=${GITHUB_REF#refs/tags/aam-backend-service/}" >> $GITHUB_ENV - - - name: Extract tag (pull request) - if: ${{ github.event.ref == '' }} - run: echo "TAG=pr-${{ github.event.number }}" >> $GITHUB_ENV - - - name: Docker meta - id: meta - uses: docker/metadata-action@v6 - with: - tags: | - type=ref,enable=true,event=pr - type=ref,enable=true,pattern={{raw}},priority=600,value=${{ env.TAG }},event=branch - # {{version}} rather than {{raw}}: plain versions (e.g. `1.22.14`) like those of - # ndb-core, also for a rebuild of an older `v`-prefixed tag. - type=semver,enable=true,pattern={{version}},priority=600,value=${{ env.TAG }},event=tag - # Moving tag for the latest release, like ndb-core's; `latest` is added - # automatically for semver tags. - type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/') }} - images: ${{ env.REGISTRY_IMAGE }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Create manifest list and push - working-directory: ./tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) + secrets: + # SENTRY_UPLOAD_SOURCES below lets the Gradle build inside the image upload this build's source bundle, so + # Sentry can show our source code next to the stack trace frames (see the Dockerfile). Not for pull requests: + # those images are never deployed, and forks cannot read the secret anyway. Keep this condition in sync with + # SENTRY_UPLOAD_SOURCES: a build that is told to upload but gets no token fails on purpose, rather than + # quietly skipping the upload. + BUILD_SECRETS: ${{ github.event_name != 'pull_request' && format('sentry_auth_token={0}', secrets.SENTRY_TOKEN) || '' }} + with: + images: ghcr.io/aam-digital/aam-services + context: application/aam-backend-service + version: ${{ needs.image-config.outputs.version }} + stable: ${{ needs.image-config.outputs.stable == 'true' }} + tags: ${{ needs.image-config.outputs.tags }} + platforms: ${{ needs.image-config.outputs.platforms }} + build-args: | + APPLICATION_VERSION=${{ needs.image-config.outputs.application-version }} + SENTRY_UPLOAD_SOURCES=${{ github.event_name != 'pull_request' }} + # PRs from forks cannot push; only check that the image builds + push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + + # After a full release, the edge images (`*-edge.*`) of the finished cycle are no longer needed. The images `edge`, + # `main`, `stable` and `latest` point to are kept. + prune-edge-images: + if: startsWith(github.ref, 'refs/tags/aam-backend-service/') + needs: build + uses: Aam-Digital/.github/.github/workflows/image-cleanup.yml@v1 + permissions: + actions: write + packages: write + with: + packages: aam-services + tags: "*-edge.*" publish: name: Publish aam-backend-service Release - runs-on: ubuntu-latest # Only main pushes compute and create the next release; the tag push this job creates - # re-triggers this workflow (to build the version-tagged image in the build/merge jobs - # above) but must not re-run this job, or it wastes the concurrency slot below re-doing - # a no-op release computation. + # re-triggers this workflow (to build the version-tagged image in the build job above) + # but must not re-run this job. if: ${{ github.ref == 'refs/heads/main' }} # release + # `build` too: PR images are linux/amd64 only, so the main build is the first arm64 build of a change. + # A release whose tag-push image build would fail on arm64 must not be created. needs: - test - # Serializes releases across overlapping main-branch pushes (e.g. several PRs merged - # within minutes of each other). Without this, two runs can both read the same - # last-released tag before either creates the next one and race to create the same - # release, so the second's `gh api` call 422s on "already_exists". - concurrency: - group: aam-backend-service-publish - cancel-in-progress: false + - build + uses: ./.github/workflows/_module-release.yml permissions: - contents: write # to be able to publish a GitHub release - defaults: - run: - working-directory: application/aam-backend-service - steps: - - name: Checkout - uses: actions/checkout@v7 - with: - fetch-depth: 0 - fetch-tags: true - - - name: Install Rust toolchain - run: rustup update --no-self-update - - - name: Install conventional_commits_next_version - run: cargo install conventional_commits_next_version --version ^7.0.0 - - - name: Set current tag and version - run: | - # Releases are tagged `aam-backend-service/`; the `v*` match finds the - # older `aam-backend-service/v` tags until the first release without it. - CURRENT_TAG=$(git describe --abbrev=0 --tags --match 'aam-backend-service/[0-9]*' --match 'aam-backend-service/v*') - CURRENT_VERSION=${CURRENT_TAG#aam-backend-service/} - CURRENT_VERSION=${CURRENT_VERSION#v} - echo "CURRENT_TAG=$CURRENT_TAG" >> "$GITHUB_ENV" - echo "CURRENT_VERSION=$CURRENT_VERSION" >> "$GITHUB_ENV" - - - name: Set from commit hash - run: | - FROM_COMMIT_HASH=$(git rev-list -n 1 "$CURRENT_TAG") - echo "FROM_COMMIT_HASH=$FROM_COMMIT_HASH" >> "$GITHUB_ENV" - - - name: Calculate next version - env: - MODULE_PATH: application/aam-backend-service - run: | - # conventional_commits_next_version (>= 7.1) fails if no commit in the range touches - # MODULE_PATH, so keep the current version (= no release) in that case. - if [ -z "$(git rev-list --first-parent --max-count=1 "$FROM_COMMIT_HASH"..HEAD -- ":(top)$MODULE_PATH")" ]; then - NEXT_VERSION=$CURRENT_VERSION - else - NEXT_VERSION=$(conventional_commits_next_version \ - --calculation-mode "Batch" \ - --from-version "$CURRENT_VERSION" \ - --monorepo "$MODULE_PATH" \ - "$FROM_COMMIT_HASH") - fi - echo "NEXT_VERSION=$NEXT_VERSION" >> "$GITHUB_ENV" - - - name: Create Release - if: env.CURRENT_VERSION != env.NEXT_VERSION - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - run: | - TAG_NAME=aam-backend-service/$NEXT_VERSION - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - /repos/Aam-Digital/aam-services/releases \ - -f "tag_name=$TAG_NAME" \ - -f "target_commitish=$GITHUB_SHA" \ - -f "name=$TAG_NAME" \ - -F "draft=false" \ - -F "prerelease=false" \ - -F "generate_release_notes=true" + contents: read + with: + module: aam-backend-service + secrets: + token: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml new file mode 100644 index 00000000..833443e6 --- /dev/null +++ b/.github/workflows/actionlint.yml @@ -0,0 +1,16 @@ +name: actionlint + +on: + pull_request: + push: + branches: + - main + +permissions: {} + +jobs: + # Check name: `actionlint / actionlint` + actionlint: + uses: Aam-Digital/.github/.github/workflows/actionlint.yml@v1 + permissions: + contents: read diff --git a/.github/workflows/keycloak-third-party-authentication-build-and-publish.yml b/.github/workflows/keycloak-third-party-authentication-build-and-publish.yml index 95c5c11b..3ca10e2c 100644 --- a/.github/workflows/keycloak-third-party-authentication-build-and-publish.yml +++ b/.github/workflows/keycloak-third-party-authentication-build-and-publish.yml @@ -1,9 +1,13 @@ name: keycloak-third-party-authentication-build-and-publish +# The module is a Keycloak extension: it is published as the jar attached to the GitHub release +# `keycloak-third-party-authentication/v` (no container image), which keycloak-aam downloads. + on: pull_request: paths: - '.github/workflows/keycloak-third-party-authentication-build-and-publish.yml' + - '.github/workflows/_module-release.yml' - 'application/keycloak-third-party-authentication/**' push: tags: @@ -12,61 +16,34 @@ on: - main paths: - '.github/workflows/keycloak-third-party-authentication-build-and-publish.yml' + - '.github/workflows/_module-release.yml' - 'application/keycloak-third-party-authentication/**' -env: - REGISTRY_IMAGE: ghcr.io/aam-digital/keycloak-third-party-authentication + +permissions: + contents: read jobs: build: runs-on: ubuntu-latest permissions: contents: read - packages: write steps: - name: Checkout repository files uses: actions/checkout@v7 - - - name: Extract tag (release) - if: ${{ github.event.ref != '' }} - run: echo "TAG=${GITHUB_REF#refs/tags/keycloak-third-party-authentication/}" >> $GITHUB_ENV - - - name: Extract tag (pull request) - if: ${{ github.event.ref == '' }} - run: echo "TAG=pr-${{ github.event.number }}" >> $GITHUB_ENV - - - name: Docker meta - id: meta - uses: docker/metadata-action@v6 with: - tags: | - type=ref,enable=true,event=pr - type=ref,enable=true,pattern={{raw}},priority=600,value=${{ env.TAG }},event=branch - type=semver,enable=true,pattern={{raw}},priority=600,value=${{ env.TAG }},event=tag - images: ${{ env.REGISTRY_IMAGE }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v4 + persist-credentials: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - - name: Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build Image and push by digest - id: build + # The Dockerfile only builds the jar (`FROM scratch`), which is exported to ./build instead of pushed + - name: Build jar uses: docker/build-push-action@v7 with: - platforms: ${{ matrix.platform }} context: ./application/keycloak-third-party-authentication - labels: ${{ steps.meta.outputs.labels }} outputs: type=local,dest=./build - cache-from: type=gha - cache-to: type=gha,mode=min + cache-from: type=gha,scope=keycloak-third-party-authentication + cache-to: type=gha,mode=min,scope=keycloak-third-party-authentication,ignore-error=true env: SOURCE_DATE_EPOCH: 0 @@ -79,79 +56,18 @@ jobs: publish: name: Publish keycloak-third-party-authentication Release - runs-on: ubuntu-latest # Only main pushes compute and create the next release; the tag push this job creates - # re-triggers this workflow (to build the versioned artifact in the build job above) - # but must not re-run this job, or it wastes the concurrency slot below re-doing a - # no-op release computation. + # re-triggers this workflow (building the jar again) but must not re-run this job. if: ${{ github.ref == 'refs/heads/main' }} # release needs: - build - # Serializes releases across overlapping main-branch pushes (e.g. several PRs merged - # within minutes of each other). Without this, two runs can both read the same - # last-released tag before either creates the next one and race to create the same - # release. - concurrency: - group: keycloak-third-party-authentication-publish - cancel-in-progress: false + uses: ./.github/workflows/_module-release.yml permissions: - contents: write # to be able to publish a GitHub release - defaults: - run: - working-directory: application/keycloak-third-party-authentication - steps: - - name: Checkout - uses: actions/checkout@v7 - with: - fetch-depth: 0 - fetch-tags: true - - - uses: actions/download-artifact@v8 - with: - path: application/keycloak-third-party-authentication - name: keycloak-third-party-authentication.jar - - - name: Install Rust toolchain - run: rustup update --no-self-update - - - name: Install conventional_commits_next_version - run: cargo install conventional_commits_next_version --version ^7.0.0 - - - name: Set current tag and version - run: | - CURRENT_TAG=$(git describe --abbrev=0 --tags --match 'keycloak-third-party-authentication/v*') - CURRENT_VERSION=$(echo $CURRENT_TAG | cut -d'/' -f2) - echo "CURRENT_TAG=$CURRENT_TAG" >> "$GITHUB_ENV" - echo "CURRENT_VERSION=$CURRENT_VERSION" >> "$GITHUB_ENV" - - - name: Set from commit hash - run: | - FROM_COMMIT_HASH=$(git rev-list -n 1 "$CURRENT_TAG") - echo "FROM_COMMIT_HASH=$FROM_COMMIT_HASH" >> "$GITHUB_ENV" - - - name: Calculate next version - env: - MODULE_PATH: application/keycloak-third-party-authentication - run: | - # conventional_commits_next_version (>= 7.1) fails if no commit in the range touches - # MODULE_PATH, so keep the current version (= no release) in that case. - if [ -z "$(git rev-list --first-parent --max-count=1 "$FROM_COMMIT_HASH"..HEAD -- ":(top)$MODULE_PATH")" ]; then - NEXT_VERSION=$CURRENT_VERSION - else - NEXT_VERSION=v$(conventional_commits_next_version \ - --calculation-mode "Batch" \ - --from-version "$CURRENT_VERSION" \ - --monorepo "$MODULE_PATH" \ - "$FROM_COMMIT_HASH") - fi - echo "NEXT_VERSION=$NEXT_VERSION" >> "$GITHUB_ENV" - echo "$NEXT_VERSION" - - - name: Create Release - if: env.CURRENT_VERSION != env.NEXT_VERSION - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - run: | - TAG_NAME=keycloak-third-party-authentication/$NEXT_VERSION - gh release create "$TAG_NAME" --target "$GITHUB_SHA" --generate-notes - gh release upload "$TAG_NAME" keycloak-third-party-authentication.jar + contents: read + with: + module: keycloak-third-party-authentication + # keycloak-aam downloads the jar from the release `keycloak-third-party-authentication/v` + version-prefix: v + asset-artifact: keycloak-third-party-authentication.jar + secrets: + token: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/pr-closed.yml b/.github/workflows/pr-closed.yml new file mode 100644 index 00000000..af540fdb --- /dev/null +++ b/.github/workflows/pr-closed.yml @@ -0,0 +1,22 @@ +name: PR closed + +on: + pull_request: + types: [closed] + +permissions: {} + +jobs: + # Deletes the `pr-` image of the PR (built by aam-backend-service-build-and-publish.yml), then the untagged + # images older than 2 days: those of earlier PR pushes and previous `main` images. + # Check name: `cleanup-image / cleanup` + cleanup-image: + # PRs from forks have no image (and their token cannot delete packages) + if: github.event.pull_request.head.repo.full_name == github.repository + uses: Aam-Digital/.github/.github/workflows/image-cleanup.yml@v1 + permissions: + actions: write + packages: write + with: + packages: aam-services + build-workflows: aam-backend-service-build-and-publish.yml diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml new file mode 100644 index 00000000..11aeff97 --- /dev/null +++ b/.github/workflows/pr-title-lint.yml @@ -0,0 +1,16 @@ +name: PR title lint + +on: + # pull_request_target so that it also runs for PRs from forks. Safe here: the workflow never checks out PR code + # and only reads the PR title from the event payload. + pull_request_target: # zizmor: ignore[dangerous-triggers] never checks out PR code, only reads the PR title from the event payload + types: [opened, edited, synchronize, reopened] + +permissions: {} + +jobs: + # Check name: `pr-title / Validate PR title` + pr-title: + uses: Aam-Digital/.github/.github/workflows/pr-title-lint.yml@v1 + permissions: + pull-requests: read diff --git a/renovate.json b/renovate.json index 140e4641..d9c1b74e 100644 --- a/renovate.json +++ b/renovate.json @@ -1,8 +1,7 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "config:recommended", - "group:allNonMajor" + "local>Aam-Digital/.github//renovate/default" ], "enabledManagers": [ "gradle", @@ -13,30 +12,6 @@ "docker-compose", "custom.regex" ], - "timezone": "Etc/UTC", - "schedule": [ - "* * * * 0" - ], - "dependencyDashboard": false, - "prConcurrentLimit": 5, - "prHourlyLimit": 2, - "labels": [ - "dependencies" - ], - "reviewers": [ - "sleidig" - ], - "vulnerabilityAlerts": { - "enabled": true, - "schedule": [ - "at any time" - ], - "labels": [ - "dependencies", - "security" - ] - }, - "osvVulnerabilityAlerts": true, "customManagers": [ { "customType": "regex", @@ -67,20 +42,10 @@ "matchUpdateTypes": [ "major" ], - "draftPR": true, - "labels": [ - "dependencies", + "addLabels": [ "major-update" ] }, - { - "matchUpdateTypes": [ - "minor", - "patch" - ], - "groupName": "weekly-non-major-updates", - "automerge": false - }, { "description": "The JVM build and runtime toolchain. The Gradle wrapper and the gradle builder image pin the same version in two files handled by different managers, and the amazoncorretto runtime image has to stay on the same JDK line the service is built against. Grouping them means a toolchain move arrives as one reviewable PR instead of several that can land apart.", "groupName": "jvm-toolchain", @@ -98,8 +63,7 @@ "com.github.dasniko:testcontainers-keycloak" ], "draftPR": true, - "labels": [ - "dependencies", + "addLabels": [ "needs-deploy-coordination" ] },