diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 6d2d1100bb7..56783ba5d5f 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -14,198 +14,21 @@ permissions: contents: read jobs: - cleanup-abandoned: - name: Delete abandoned automation branch - if: > - github.event_name == 'pull_request' && - github.event.pull_request.merged == false && - github.event.pull_request.head.repo.full_name == github.repository && - (startsWith(github.event.pull_request.head.ref, 'release/prep-') || - startsWith(github.event.pull_request.head.ref, 'chore/next-snapshot-')) - runs-on: ubuntu-latest + publish: + name: Tag, deploy to Maven Central and advance develop + # Reusable workflow: secure-software-engineering/actions/release/README.md + uses: secure-software-engineering/actions/release/maven-release-publish/action.yml@d039e33678c4515ae4f9664e9fe9db33b84a6419 # develop permissions: contents: write - steps: - - name: Delete abandoned release branch - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - HEAD_REF: ${{ github.event.pull_request.head.ref }} - run: | - gh api -X DELETE "repos/${REPO}/git/refs/heads/${HEAD_REF}" \ - || echo "Branch already gone, nothing to do." - - - release: - name: Tag and deploy to Maven Central - concurrency: - group: release-publish-deploy - cancel-in-progress: false - if: > - github.event_name == 'push' -# && -# startsWith(github.event.head_commit.message, 'Merge pull request') && -# contains(github.event.head_commit.message, '/release/prep-') - runs-on: ubuntu-latest - environment: deployment - permissions: - contents: write - steps: - - name: Checkout develop - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - token: ${{ secrets.AUTO_MERGE_PAT }} - fetch-depth: 0 - - - name: Setup Java - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 - with: - distribution: adopt - java-package: jdk - java-version: 17 - server-id: central # must match the serverId configured for the nexus-staging-maven-plugin - server-username: MAVEN_USERNAME # Env var that holds the central publisher user name - server-password: MAVEN_CENTRAL_TOKEN # Env var that holds the central publisher user token - gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} # Substituted with the value stored in the referenced secret - gpg-passphrase: MAVEN_GPG_PASSPHRASE # Env var that holds the key's passphrase - - - name: Determine version and check for existing tag - id: version - run: | - set -euo pipefail - # develop's tip now holds whatever version the merged release PR brought in - CURRENT_VERSION=$(mvn -ntp org.apache.maven.plugins:maven-help-plugin:2.1.1:evaluate -Dexpression=project.version -DforceStdout 2>/dev/null | grep -v '^\[' | tail -1) - TAG_NAME="v$CURRENT_VERSION" - echo "version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" - echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" - - if [[ "$CURRENT_VERSION" == *-SNAPSHOT ]]; then - echo "is_release=false" >> "$GITHUB_OUTPUT" - echo "::notice::develop is still on a SNAPSHOT version ($CURRENT_VERSION) after a release/prep merge; skipping release." - elif git ls-remote --exit-code --tags origin "refs/tags/$TAG_NAME" >/dev/null 2>&1; then - echo "is_release=false" >> "$GITHUB_OUTPUT" - echo "::notice::Tag $TAG_NAME already exists; skipping release (version already released)." - else - echo "is_release=true" >> "$GITHUB_OUTPUT" - fi - - - name: Release on Maven Central - if: steps.version.outputs.is_release == 'true' - run: | - set -uo pipefail - # -U force updates just to make sure we are using latest dependencies - # -B Batch mode (do not ask for user input), just in case - # -D activate a profile via the release property (and disable defined submodules that should not be released) - mvn -U -B -ntp clean deploy -Drelease -DskipTests - STATUS=$? - - if [[ "$STATUS" -ne 0 ]]; then - # The tag-exists check in the previous step only catches a fully-successful - # prior run (the tag is only created after this step succeeds). It does NOT - # catch a prior run that failed partway through this multi-module deploy - - # some modules published, others not. Maven Central artifacts are immutable, - # so a retry can hit "already exists" for whichever modules got through. - echo "::error::mvn deploy failed (exit $STATUS)." - echo "If the error above mentions artifacts already existing, a previous run likely partially published this version before failing - Central artifacts are immutable, so a retry can't safely resume." - echo "In that case: bump to a new version and re-run the release process instead of retrying this one." - exit "$STATUS" - fi - env: - MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PRIVATE_KEY_PASSPHRASE }} - - - name: Tag the release version - if: success() && steps.version.outputs.is_release == 'true' - env: - ACTOR: ${{ github.actor }} - CURRENT_VERSION: ${{ steps.version.outputs.version }} - TAG_NAME: ${{ steps.version.outputs.tag_name }} - run: | - set -euo pipefail - git config --global user.email "${ACTOR}@users.noreply.github.com" - git config --global user.name "$ACTOR" - - git tag -a "$TAG_NAME" -m "version $CURRENT_VERSION" - git push origin "$TAG_NAME" - - - name: Create GitHub release - if: success() && steps.version.outputs.is_release == 'true' - run: | - gh release create "$TAG_NAME" --title "$TAG_NAME" --generate-notes - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ steps.version.outputs.tag_name }} - - outputs: - tag_name: ${{ steps.version.outputs.tag_name }} - is_release: ${{ steps.version.outputs.is_release }} - - - bump-develop: - name: Advance develop to next SNAPSHOT - needs: release - if: success() && needs.release.outputs.is_release == 'true' - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout develop - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - token: ${{ secrets.AUTO_MERGE_PAT }} - ref: develop - fetch-depth: 0 - - - name: Setup Java - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 - with: - distribution: zulu - java-version: '17' - cache: 'maven' - - - name: Bump to next SNAPSHOT and open auto-merge PR - env: - GH_TOKEN: ${{ secrets.AUTO_MERGE_PAT }} - ACTOR: ${{ github.actor }} - RUN_ID: ${{ github.run_id }} - RELEASE_TAG_NAME: ${{ needs.release.outputs.tag_name }} - run: | - set -euo pipefail - git config --global user.email "${ACTOR}@users.noreply.github.com" - git config --global user.name "$ACTOR" - - BRANCH="chore/next-snapshot-${RUN_ID}" - git checkout -b "$BRANCH" - - # Bumps forward from the version that was just released (develop's current tip), e.g. 2.4.0 -> 2.4.1-SNAPSHOT - mvn -B -ntp versions:set versions:commit -DnextSnapshot - git ls-files | grep 'pom.xml$' | xargs git add - git commit --allow-empty -am "prepare next development iteration" - # --force: BRANCH is deterministic (tied to run_id) and owned exclusively - # by this workflow run. A retry after a later step failed recreates this - # commit with a new SHA - a plain push would be rejected as non-fast-forward - # against the branch left over from the earlier attempt. - git push --force origin "$BRANCH" - - # A retry after a prior attempt already got as far as opening the PR (but - # failed before/at the auto-merge call below) must reuse that PR instead of - # trying to create a second one, which `gh pr create` would reject. - EXISTING_PR=$(gh pr list -B develop -H "$BRANCH" --state open --json number -q '.[0].number // empty') - if [[ -z "$EXISTING_PR" ]]; then - gh pr create \ - -B develop \ - -H "$BRANCH" \ - -t "Prepare next development iteration after ${RELEASE_TAG_NAME}" \ - -b "Advances develop's SNAPSHOT version following the ${RELEASE_TAG_NAME} release." - PR_NUMBER=$(gh pr view "$BRANCH" --json number -q .number) - else - PR_NUMBER="$EXISTING_PR" - echo "Reusing existing open PR #$PR_NUMBER for $BRANCH (retry)." - fi - - # --delete-branch is rejected by `gh pr merge` when the base branch has a - # merge queue enabled (develop does): the merge queue - not this command - - # performs the actual merge, so branch cleanup must be left to the repo's - # "Automatically delete head branches" setting instead. - gh pr merge "$PR_NUMBER" --auto --merge + with: + head_branch: develop + release_java_distribution: adopt + prepare_java_distribution: zulu + maven_server_id: central + deployment_environment: deployment + secrets: + release_pat: ${{ secrets.AUTO_MERGE_PAT }} + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg_passphrase: ${{ secrets.GPG_PRIVATE_KEY_PASSPHRASE }} + maven_username: ${{ secrets.MAVEN_USERNAME }} + maven_central_token: ${{ secrets.MAVEN_CENTRAL_TOKEN }} diff --git a/.github/workflows/release-title-sync.yml b/.github/workflows/release-title-sync.yml index 7f33521ee77..62970acd783 100644 --- a/.github/workflows/release-title-sync.yml +++ b/.github/workflows/release-title-sync.yml @@ -9,108 +9,17 @@ on: permissions: contents: read -concurrency: - group: release-title-sync-${{ github.event.pull_request.number }} - cancel-in-progress: true - jobs: sync: name: Recompute release version from title tag # head.repo check rejects fork PRs pretending to be our own release branches - # legitimate release/prep-* branches are always pushed by our own automation into this repo. + # (Fork check itself is enforced inside the reusable workflow too; this is just + # a cheap skip so we don't even spin up a job run for irrelevant PRs.) if: > startsWith(github.head_ref, 'release/prep-') && github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - permissions: - contents: read - env: - TITLE: ${{ github.event.pull_request.title }} - steps: - - name: Checkout release branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - token: ${{ secrets.AUTO_MERGE_PAT }} # default GITHUB_TOKEN is blocked by branch protection - ref: ${{ github.head_ref }} - fetch-depth: 0 - - - name: Setup Java - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 - with: - distribution: zulu - java-version: '17' - cache: 'maven' - - - name: Parse release type tag from title - id: parsed - run: | - set -euo pipefail - FOUND=() - shopt -s nocasematch - [[ "$TITLE" == *"[major]"* ]] && FOUND+=("major") - [[ "$TITLE" == *"[minor]"* ]] && FOUND+=("minor") - [[ "$TITLE" == *"[patch]"* ]] && FOUND+=("patch") - - if [[ ${#FOUND[@]} -gt 1 ]]; then - echo "PR title contains conflicting version tags: ${FOUND[*]}. Use only one of [major]/[minor]/[patch]." >&2 - exit 1 - elif [[ ${#FOUND[@]} -eq 1 ]]; then - echo "release_type=${FOUND[0]}" >> "$GITHUB_OUTPUT" - else - echo "PR title must contain exactly one of [major]/[minor]/[patch]." >&2 - exit 1 - fi - - - name: Recompute release version from latest tag - id: version - env: - RELEASE_TYPE: ${{ steps.parsed.outputs.release_type }} - run: | - set -euo pipefail - git fetch --tags - LATEST_VERSION=$(git tag --list 'v*' | sed 's/^v//' | sort -V | tail -1) - - if [[ -z "$LATEST_VERSION" ]]; then - echo "No existing vX.Y.Z tag found in repo" >&2 - exit 1 - fi - - if [[ "$LATEST_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then - MAJOR="${BASH_REMATCH[1]}" - MINOR="${BASH_REMATCH[2]}" - PATCH="${BASH_REMATCH[3]}" - else - echo "Could not parse latest tag version '$LATEST_VERSION' as semver" >&2 - exit 1 - fi - - case "$RELEASE_TYPE" in - major) RELEASE_VERSION="$((MAJOR + 1)).0.0" ;; - minor) RELEASE_VERSION="$MAJOR.$((MINOR + 1)).0" ;; - patch) RELEASE_VERSION="$MAJOR.$MINOR.$((PATCH + 1))" ;; - esac - - echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT" - - - name: Amend release branch if version changed - env: - ACTOR: ${{ github.actor }} - RELEASE_VERSION: ${{ steps.version.outputs.release_version }} - HEAD_REF: ${{ github.head_ref }} - run: | - set -euo pipefail - git config --global user.email "${ACTOR}@users.noreply.github.com" - git config --global user.name "$ACTOR" - - CURRENT_VERSION=$(mvn -ntp org.apache.maven.plugins:maven-help-plugin:2.1.1:evaluate -Dexpression=project.version -DforceStdout 2>/dev/null | grep -v '^\[' | tail -1) - - if [[ "$CURRENT_VERSION" == "$RELEASE_VERSION" ]]; then - echo "Release version already up to date ($CURRENT_VERSION)." - exit 0 - fi - - echo "Updating release branch from $CURRENT_VERSION to $RELEASE_VERSION" - mvn -B -ntp build-helper:parse-version versions:set -DnewVersion="$RELEASE_VERSION" versions:commit - git ls-files | grep 'pom.xml$' | xargs git add - git commit --amend -am "release $RELEASE_VERSION" - git push --force origin HEAD:"$HEAD_REF" + # Reusable workflow: secure-software-engineering/actions/release/README.md + uses: secure-software-engineering/actions/release/maven-release-title-sync/action.yml@d039e33678c4515ae4f9664e9fe9db33b84a6419 # develop + secrets: + release_pat: ${{ secrets.AUTO_MERGE_PAT }} # default GITHUB_TOKEN is blocked by branch protection diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03c67fe4c5c..2e9c571c639 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,107 +12,17 @@ on: - major - minor - patch - workflow_call: - inputs: - release_type: - description: 'Release type' - required: true - type: string + permissions: contents: read -concurrency: - group: release - cancel-in-progress: false - -env: - HEAD_BRANCH: develop - jobs: prepare-release: name: Open release PR against develop - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout develop - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - token: ${{ secrets.AUTO_MERGE_PAT }} # default GITHUB_TOKEN is blocked by branch protection / can't trigger downstream workflows - ref: ${{ env.HEAD_BRANCH }} - fetch-depth: 0 - - - name: Setup Java - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 - with: - distribution: zulu - java-version: '17' - cache: 'maven' - - - name: Determine initial release version from latest tag - id: version - run: | - set -euo pipefail - LATEST_VERSION=$(git tag --list 'v*' | sed 's/^v//' | sort -V | tail -1) - - if [[ -z "$LATEST_VERSION" ]]; then - echo "No existing vX.Y.Z tag found in repo" >&2 - exit 1 - fi - - if [[ "$LATEST_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then - MAJOR="${BASH_REMATCH[1]}" - MINOR="${BASH_REMATCH[2]}" - PATCH="${BASH_REMATCH[3]}" - else - echo "Could not parse latest tag version '$LATEST_VERSION' as semver" >&2 - exit 1 - fi - - case "${{ inputs.release_type }}" in - major) RELEASE_VERSION="$((MAJOR + 1)).0.0" ;; - minor) RELEASE_VERSION="$MAJOR.$((MINOR + 1)).0" ;; - patch) RELEASE_VERSION="$MAJOR.$MINOR.$((PATCH + 1))" ;; - *) - echo "Unknown release_type '${{ inputs.release_type }}'" >&2 - exit 1 - ;; - esac - - echo "Latest released version: $LATEST_VERSION -> proposed $RELEASE_VERSION (${{ inputs.release_type }})" - echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT" - - - name: Create release branch and bump version - id: branch - run: | - set -euo pipefail - git config --global user.email "${{ github.actor }}@users.noreply.github.com" - git config --global user.name "${{ github.actor }}" - - RELEASE_BRANCH="release/prep-${{ github.run_id }}" - git checkout -b "$RELEASE_BRANCH" - mvn -B -ntp build-helper:parse-version versions:set -DnewVersion="${{ steps.version.outputs.release_version }}" versions:commit - git ls-files | grep 'pom.xml$' | xargs git add - git commit -am "release ${{ steps.version.outputs.release_version }}" - # --force: RELEASE_BRANCH is deterministic (tied to run_id) and owned - # exclusively by this workflow run. A "re-run failed jobs" retry after a - # later step failed (e.g. PR creation) recreates this commit with a new - # SHA - a plain push would be rejected as non-fast-forward against the - # branch left over from the earlier attempt. - git push --force origin "$RELEASE_BRANCH" - - echo "release_branch=$RELEASE_BRANCH" >> "$GITHUB_OUTPUT" - - - name: Open PR against develop - env: - GH_TOKEN: ${{ secrets.AUTO_MERGE_PAT }} - run: | - gh pr create \ - -B "${{ env.HEAD_BRANCH }}" \ - -H "${{ steps.branch.outputs.release_branch }}" \ - -t "[${{ inputs.release_type }}] Release ${{ steps.version.outputs.release_version }}" \ - -b "Release candidate for \`${{ steps.version.outputs.release_version }}\`. - - To change the release type, edit the title's \`[${{ inputs.release_type }}]\` tag to \`[major]\`, \`[minor]\` or \`[patch]\` - the version and this branch update automatically. - - **This PR must be merged manually.** Merging it tags \`v${{ steps.version.outputs.release_version }}\` (adjusted if you edited the type tag), deploys to Maven Central, and opens a follow-up PR advancing \`develop\` to the next SNAPSHOT (that one auto-merges)." + # Reusable workflow: secure-software-engineering/actions/release/README.md + uses: secure-software-engineering/actions/release/maven-release-prepare/action.yml@d039e33678c4515ae4f9664e9fe9db33b84a6419 # develop + with: + release_type: ${{ inputs.release_type }} + head_branch: develop + secrets: + release_pat: ${{ secrets.AUTO_MERGE_PAT }} # default GITHUB_TOKEN is blocked by branch protection / can't trigger downstream workflows