From 52bd30d7415eba7b6012c19e4c17cf4f2dfb53dc Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 14 Jul 2026 17:09:13 +0100 Subject: [PATCH 1/5] build: Add maven-release-plugin and SCM config to all modules Add maven-release-plugin configuration with module-specific tag formats and SCM connection details to enable automated Maven deployments across all library modules. --- aws-lambda-java-core/pom.xml | 20 +++++++++++++++++++ .../pom.xml | 15 ++++++++++++++ aws-lambda-java-events/pom.xml | 20 +++++++++++++++++++ aws-lambda-java-log4j2/pom.xml | 15 ++++++++++++++ .../pom.xml | 15 ++++++++++++++ aws-lambda-java-serialization/pom.xml | 15 ++++++++++++++ aws-lambda-java-tests/pom.xml | 15 ++++++++++++++ 7 files changed, 115 insertions(+) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index cca9d0cdf..b0277d3e0 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -22,6 +22,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -36,6 +39,22 @@ 1.8 + + + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-core-@{project.version} + true + release + deploy + + + + + dev @@ -114,6 +133,7 @@ true central + true diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index 6de599ef7..d05cd63d5 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -24,6 +24,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -79,6 +82,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-events-sdk-transformer-@{project.version} + true + release + deploy + + maven-surefire-plugin ${maven-surefire-plugin.version} @@ -171,6 +185,7 @@ true central + true diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index c8c40e0c7..9c19cdc05 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -22,6 +22,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -83,6 +86,22 @@ + + + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-events-@{project.version} + true + release + deploy + + + + + dev @@ -161,6 +180,7 @@ true central + true diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index a03d3d3b6..fd0ce8114 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -22,6 +22,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -77,6 +80,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-log4j2-@{project.version} + true + release + deploy + + org.apache.maven.plugins maven-toolchains-plugin @@ -183,6 +197,7 @@ true central + true diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 2ba71c43c..fa580e7d1 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -21,6 +21,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -115,6 +118,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-runtime-interface-client-@{project.version} + true + release + deploy + + maven-install-plugin org.apache.maven.plugins @@ -381,6 +395,7 @@ true central + true diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 60a3c67a9..74b9f669c 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -19,6 +19,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -175,6 +178,7 @@ true central + true @@ -191,6 +195,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-serialization-@{project.version} + true + release + deploy + + org.apache.maven.plugins maven-shade-plugin diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 5e5f19b33..c0eef7929 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -20,6 +20,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -236,6 +239,7 @@ true central + true @@ -245,6 +249,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-tests-@{project.version} + true + release + deploy + + org.apache.maven.plugins maven-compiler-plugin From 544ee077db405850e8c28bc7d926118268a8f58f Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Wed, 15 Jul 2026 17:59:03 +0100 Subject: [PATCH 2/5] ci: Add automated Maven Central release workflow Add .github/workflows/release.yml, a workflow_dispatch pipeline that builds, tests, and publishes a single module to Maven Central in one pinned JDK 8 environment, replacing the manual mvn deploy from a developer workstation. The release step prepares locally, publishes, then pushes the commits and module-scoped tag atomically, so a failed publish never leaves an orphan tag on the remote; a failure path rolls back the runner state. Releases are serialized repo-wide via a shared concurrency group. Signing/publishing secrets are not wired yet, so only the dry-run path (skip_publish) works without credentials for now. Set all module versions to -SNAPSHOT as the release plugin's expected starting point, and merge a duplicate top-level section in the events POM introduced when the toolchains change (PR #617) met the release-plugin config on this branch. --- .github/workflows/release.yml | 187 ++++++++++++++++++ aws-lambda-java-core/pom.xml | 2 +- .../pom.xml | 2 +- aws-lambda-java-events/pom.xml | 29 ++- aws-lambda-java-log4j2/pom.xml | 2 +- .../pom.xml | 2 +- aws-lambda-java-serialization/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- 8 files changed, 205 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..6d899990b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,187 @@ +name: Release to Maven Central + +# Automated release pipeline: builds, tests, and publishes a module to Maven +# Central in one controlled environment. Signing/publishing secrets are still +# to be handled; until then use skip_publish (dry-run), which needs none. + +on: + workflow_dispatch: + inputs: + module: + description: 'Module to release (directory name, e.g. aws-lambda-java-log4j2)' + required: true + type: choice + options: + - aws-lambda-java-core + - aws-lambda-java-events + - aws-lambda-java-events-sdk-transformer + - aws-lambda-java-log4j2 + - aws-lambda-java-runtime-interface-client + - aws-lambda-java-serialization + - aws-lambda-java-tests + releaseVersion: + description: 'Release version override (optional; defaults to the POM version without -SNAPSHOT)' + required: false + type: string + developmentVersion: + description: 'Next development version override (optional, must end with -SNAPSHOT)' + required: false + type: string + skip_publish: + description: 'Skip publish (dry-run validation)' + required: false + type: boolean + default: false + +permissions: + contents: write # push release commits and tag + id-token: write # reserved for future secrets handling + +# Serialize all releases repo-wide to avoid concurrent pushes racing on the +# default branch. Never cancel in-flight: it could leave a half-published state. +concurrency: + group: release + cancel-in-progress: false + +env: + MODULE: ${{ github.event.inputs.module }} + RELEASE_VERSION_INPUT: ${{ github.event.inputs.releaseVersion }} + DEVELOPMENT_VERSION_INPUT: ${{ github.event.inputs.developmentVersion }} + # Batch mode + no transfer-progress spam for every Maven call (Maven 3.9+). + MAVEN_ARGS: "-B --no-transfer-progress" + +jobs: + release: + runs-on: ubuntu-latest + environment: Release + timeout-minutes: 30 + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 # full history for tagging/pushing + + # Pinned JDK 8: building on a newer JDK can silently break the artifact. + - name: Set up JDK 8 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: 8 + distribution: corretto + cache: maven + + - name: Validate inputs and resolve versions + run: | + if [[ ! -d "$MODULE" ]]; then + echo "::error::Module directory '$MODULE' does not exist" + exit 1 + fi + if [[ ! -f "$MODULE/pom.xml" ]]; then + echo "::error::No pom.xml found in '$MODULE'" + exit 1 + fi + + # The POM version is the source of truth and must be a SNAPSHOT. + CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") + CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" + if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then + echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" + exit 1 + fi + + # releaseVersion input is an optional override; default strips -SNAPSHOT. + EFFECTIVE_RELEASE_VERSION="${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" + + if [[ -n "$DEVELOPMENT_VERSION_INPUT" && "$DEVELOPMENT_VERSION_INPUT" != *-SNAPSHOT ]]; then + echo "::error::developmentVersion '$DEVELOPMENT_VERSION_INPUT' must end with -SNAPSHOT" + exit 1 + fi + + # Build the release plugin version args once; reused by both paths. + RELEASE_ARGS="-DreleaseVersion=$EFFECTIVE_RELEASE_VERSION" + if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then + RELEASE_ARGS="$RELEASE_ARGS -DdevelopmentVersion=$DEVELOPMENT_VERSION_INPUT" + fi + + echo "EFFECTIVE_RELEASE_VERSION=$EFFECTIVE_RELEASE_VERSION" >> "$GITHUB_ENV" + echo "RELEASE_ARGS=$RELEASE_ARGS" >> "$GITHUB_ENV" + echo "::notice::Releasing $MODULE $EFFECTIVE_RELEASE_VERSION (POM currently $CURRENT_VERSION)" + + - name: Configure git user + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # TODO(secrets): signing/publishing credentials are still to be handled. + # Until then, only skip_publish=true (dry-run) works, as it needs none. + + - name: Install intra-repo dependencies + run: | + # Modules that must be installed locally before the target builds. + declare -A DEPS + DEPS[aws-lambda-java-core]="" + DEPS[aws-lambda-java-events]="" + DEPS[aws-lambda-java-serialization]="" + DEPS[aws-lambda-java-log4j2]="aws-lambda-java-core" + DEPS[aws-lambda-java-events-sdk-transformer]="aws-lambda-java-events" + DEPS[aws-lambda-java-runtime-interface-client]="aws-lambda-java-core aws-lambda-java-serialization" + DEPS[aws-lambda-java-tests]="aws-lambda-java-core aws-lambda-java-serialization aws-lambda-java-events" + + DEP_LIST="${DEPS[$MODULE]}" + if [[ -n "$DEP_LIST" ]]; then + for dep in $DEP_LIST; do + echo "::group::Installing dependency: $dep" + mvn install -DskipTests --file "$dep/pom.xml" + echo "::endgroup::" + done + else + echo "::notice::No intra-repo dependencies for $MODULE" + fi + + # No skip-tests option: never release an unverified artifact. + - name: Run tests + run: mvn verify --file "$MODULE/pom.xml" + + # prepare/perform aren't atomic. Prepare locally (no push), publish, then + # push only after the artifact is live, so a failed publish never leaves an + # orphan tag on the remote. On failure, the rollback step cleans the runner. + - name: Release (prepare locally, publish, then push) + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + # 1. Prepare locally (no push): create the release commits + tag. + mvn release:prepare -DpushChanges=false $RELEASE_ARGS --file "$MODULE/pom.xml" + + # 2. Publish to Maven Central from the local tag. + mvn release:perform -DlocalCheckout=true --file "$MODULE/pom.xml" + + # 3. Push commits and tag atomically (both or neither). + git push --atomic origin \ + "HEAD:${GITHUB_REF_NAME}" \ + "refs/tags/${MODULE}-${EFFECTIVE_RELEASE_VERSION}" + + - name: Dry-run release (prepare only, no publish) + if: ${{ github.event.inputs.skip_publish == 'true' }} + run: | + mvn release:prepare -DdryRun=true $RELEASE_ARGS --file "$MODULE/pom.xml" + mvn release:clean --file "$MODULE/pom.xml" || true + + # Nothing was pushed, so this only cleans the runner for a retry. + - name: Roll back release on failure + if: ${{ failure() && github.event.inputs.skip_publish != 'true' }} + run: | + mvn release:rollback --file "$MODULE/pom.xml" || true + mvn release:clean --file "$MODULE/pom.xml" || true + git tag -d "${MODULE}-${EFFECTIVE_RELEASE_VERSION}" 2>/dev/null || true + echo "::warning::Release failed before publish completed. The remote was not modified; the runner state has been rolled back. Safe to retry." + + - name: Summary + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + TAG_NAME="${MODULE}-${EFFECTIVE_RELEASE_VERSION}" + echo "## Release Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Module | \`$MODULE\` |" >> $GITHUB_STEP_SUMMARY + echo "| Version | \`$EFFECTIVE_RELEASE_VERSION\` |" >> $GITHUB_STEP_SUMMARY + echo "| Tag | \`$TAG_NAME\` |" >> $GITHUB_STEP_SUMMARY + echo "| Maven Central | [com.amazonaws:$MODULE:$EFFECTIVE_RELEASE_VERSION](https://central.sonatype.com/artifact/com.amazonaws/$MODULE/$EFFECTIVE_RELEASE_VERSION) |" >> $GITHUB_STEP_SUMMARY diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 321098784..e9464e3d1 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.4.0 + 1.4.0-SNAPSHOT jar AWS Lambda Java Core Library diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index 9b83b2e13..1072f4cc6 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events-sdk-transformer - 3.1.1 + 3.1.1-SNAPSHOT jar AWS Lambda Java Events SDK Transformer Library diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index e5dc3329c..7ab9aa938 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.16.1 + 3.16.1-SNAPSHOT jar AWS Lambda Java Events Library @@ -68,6 +68,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-events-@{project.version} + true + release + deploy + + @@ -112,22 +123,6 @@ - - - - org.apache.maven.plugins - maven-release-plugin - 3.1.1 - - aws-lambda-java-events-@{project.version} - true - release - deploy - - - - - dev diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index fd0ce8114..432c4f5c4 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-log4j2 - 1.6.4 + 1.6.4-SNAPSHOT jar AWS Lambda Java Log4j 2.x Libraries diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 66e2f2fe2..0cf33d828 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.11.0 + 2.11.0-SNAPSHOT jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 713fdfcb8..613b204c6 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-lambda-java-serialization - 1.4.1 + 1.4.1-SNAPSHOT jar AWS Lambda Java Runtime Serialization diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 3272b3f44..b1daf4105 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-tests - 1.1.3 + 1.1.3-SNAPSHOT jar AWS Lambda Java Tests From c9e1061841d88ab37b60a5879c97841b7f674b86 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 21 Jul 2026 16:53:06 +0100 Subject: [PATCH 3/5] ci: Wire OIDC secrets and add native RIC release workflow --- .../release-runtime-interface-client.yml | 288 ++++++++++++++++++ .github/workflows/release.yml | 86 ++++-- 2 files changed, 355 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/release-runtime-interface-client.yml diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml new file mode 100644 index 000000000..43090d1b7 --- /dev/null +++ b/.github/workflows/release-runtime-interface-client.yml @@ -0,0 +1,288 @@ +name: Release RIC to Maven Central + +# RIC ships a native JNI lib for 4 targets + a main JAR (5 artifacts). Each +# native lib is built on its own architecture (x86_64 on ubuntu-latest, +# aarch_64 on ubuntu-24.04-arm) instead of emulating with QEMU. A build matrix +# produces the classifier JARs, then one job assembles and publishes them. + +on: + workflow_dispatch: + inputs: + releaseVersion: + description: 'Release version override (optional; defaults to the POM version without -SNAPSHOT)' + required: false + type: string + developmentVersion: + description: 'Next development version override (optional, must end with -SNAPSHOT)' + required: false + type: string + skip_publish: + description: 'Skip publish (dry-run validation)' + required: false + type: boolean + default: false + +permissions: + contents: write # push release commit and tag + id-token: write # assume the OIDC role for secret retrieval + +# Share the repo-wide "release" group with release.yml so RIC and the pure-Java +# modules can never publish concurrently. Never cancel in-flight: it could leave +# a half-published state. +concurrency: + group: release + cancel-in-progress: false + +env: + MODULE: aws-lambda-java-runtime-interface-client + RELEASE_VERSION_INPUT: ${{ github.event.inputs.releaseVersion }} + DEVELOPMENT_VERSION_INPUT: ${{ github.event.inputs.developmentVersion }} + MAVEN_ARGS: "-B --no-transfer-progress" + AWS_REGION: ${{ vars.AWS_REGION_MAVEN_RELEASE }} + OIDC_ROLE_ARN: ${{ secrets.AWS_ROLE_MAVEN_RELEASE }} + +jobs: + # Build each architecture's native libs (glibc + musl) on a native runner. + build-natives: + strategy: + fail-fast: true + matrix: + include: + - arch: x86_64 + runner: ubuntu-latest + profiles: linux-x86_64 linux_musl-x86_64 + - arch: aarch64 + runner: ubuntu-24.04-arm + profiles: linux-aarch64 linux_musl-aarch64 + runs-on: ${{ matrix.runner }} + timeout-minutes: 45 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Set up JDK 8 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: 8 + distribution: corretto + cache: maven + + - name: Resolve release version + run: | + CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") + CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" + if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then + echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" + exit 1 + fi + echo "EFFECTIVE_RELEASE_VERSION=${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" >> "$GITHUB_ENV" + + # -DskipTests: only installed so the module compiles, not released here. + - name: Install intra-repo dependencies + run: | + for dep in aws-lambda-java-core aws-lambda-java-serialization; do + mvn install -DskipTests --file "$dep/pom.xml" + done + + # Build at the release version (matches the JAR names the release job + # attaches). + - name: Build native classifier JARs (${{ matrix.arch }}) + env: + IS_JAVA_8: true + run: | + mvn versions:set -DnewVersion="$EFFECTIVE_RELEASE_VERSION" -DgenerateBackupPoms=false --file "$MODULE/pom.xml" + for profile in ${{ matrix.profiles }}; do + echo "::group::Building $profile" + mvn package -P "$profile" -DmultiArch=false -DskipTests --file "$MODULE/pom.xml" + echo "::endgroup::" + done + + # JARs to attach + .so files to assemble the fat main JAR. + - name: Upload native artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: ric-natives-${{ matrix.arch }} + if-no-files-found: error + path: | + ${{ env.MODULE }}/target/*-linux*.jar + ${{ env.MODULE }}/target/classes/jni/*.so + + # Assemble all native builds and publish. + release: + needs: build-natives + runs-on: ubuntu-latest + environment: Release + timeout-minutes: 30 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 # full history for tagging/pushing + + - name: Set up JDK 8 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: 8 + distribution: corretto + cache: maven + + - name: Validate inputs and resolve versions + run: | + CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") + CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" + if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then + echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" + exit 1 + fi + + EFFECTIVE_RELEASE_VERSION="${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" + + # Next development version: use the override, or bump the patch. + if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then + if [[ "$DEVELOPMENT_VERSION_INPUT" != *-SNAPSHOT ]]; then + echo "::error::developmentVersion '$DEVELOPMENT_VERSION_INPUT' must end with -SNAPSHOT" + exit 1 + fi + NEXT_DEV_VERSION="$DEVELOPMENT_VERSION_INPUT" + else + IFS='.' read -r MA MI PA <<< "$EFFECTIVE_RELEASE_VERSION" + NEXT_DEV_VERSION="${MA}.${MI}.$((PA + 1))-SNAPSHOT" + fi + + echo "EFFECTIVE_RELEASE_VERSION=$EFFECTIVE_RELEASE_VERSION" >> "$GITHUB_ENV" + echo "NEXT_DEV_VERSION=$NEXT_DEV_VERSION" >> "$GITHUB_ENV" + echo "TAG_NAME=${MODULE}-${EFFECTIVE_RELEASE_VERSION}" >> "$GITHUB_ENV" + echo "::notice::Releasing $MODULE $EFFECTIVE_RELEASE_VERSION (next dev $NEXT_DEV_VERSION)" + + - name: Configure git user + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # -DskipTests: only installed so the module compiles, not released here. + - name: Install intra-repo dependencies + run: | + for dep in aws-lambda-java-core aws-lambda-java-serialization; do + mvn install -DskipTests --file "$dep/pom.xml" + done + + - name: Set release version + run: mvn versions:set -DnewVersion="$EFFECTIVE_RELEASE_VERSION" -DgenerateBackupPoms=false --file "$MODULE/pom.xml" + + # Test gate before publish. + - name: Run tests + env: + IS_JAVA_8: true + run: mvn test --file "$MODULE/pom.xml" + + # JARs to attach + .so files for the fat main JAR. + - name: Download native artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + pattern: ric-natives-* + path: ric-natives + + - name: Stage native artifacts + run: | + mkdir -p "$MODULE/target/classes/jni" + find ric-natives -name '*.jar' -exec cp {} "$MODULE/target/" \; + find ric-natives -name '*.so' -exec cp {} "$MODULE/target/classes/jni/" \; + echo "Staged native artifacts:" + ls -1 "$MODULE/target/"*-linux*.jar "$MODULE/target/classes/jni/"*.so + + - name: Configure AWS credentials (OIDC) + if: ${{ github.event.inputs.skip_publish != 'true' }} + uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 + with: + aws-region: ${{ env.AWS_REGION }} + role-to-assume: ${{ env.OIDC_ROLE_ARN }} + role-session-name: GitHubActionsRicMavenCentralRelease + role-duration-seconds: 3600 + + - name: Fetch signing key and Sonatype credentials + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + # Shared secrets from LambdaMavenDeploy; nothing stored in GitHub. + GPG_JSON=$(aws secretsmanager get-secret-value --secret-id maven.gpg.keys --query SecretString --output text) + CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) + + GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") + GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") + SONATYPE_USERNAME=$(jq -r '."maven-central-login"' <<< "$CREDS_JSON") + SONATYPE_PASSWORD=$(jq -r '."maven-central-password"' <<< "$CREDS_JSON") + echo "::add-mask::$GPG_PASSPHRASE" + echo "::add-mask::$SONATYPE_USERNAME" + echo "::add-mask::$SONATYPE_PASSWORD" + + # Import the key with loopback pinentry so Maven can sign non-interactively. + GNUPGHOME=$(mktemp -d) + chmod 700 "$GNUPGHOME" + echo "allow-loopback-pinentry" > "$GNUPGHOME/gpg-agent.conf" + echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf" + export GNUPGHOME + gpgconf --kill gpg-agent || true + gpg --batch --import <<< "$GPG_PRIVATE_KEY" + GPG_KEYNAME=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}') + + # settings.xml with the Sonatype token (server id "central"). + SETTINGS="$RUNNER_TEMP/settings.xml" + { + echo '' + echo "central" + echo "${SONATYPE_USERNAME}" + echo "${SONATYPE_PASSWORD}" + echo '' + } > "$SETTINGS" + + echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" + echo "GPG_KEYNAME=$GPG_KEYNAME" >> "$GITHUB_ENV" + echo "GPG_PASSPHRASE=$GPG_PASSPHRASE" >> "$GITHUB_ENV" + echo "MAVEN_SETTINGS=$SETTINGS" >> "$GITHUB_ENV" + + # -DmultiArch=false builds only the host .so; the aarch_64 .so is already + # staged, so the main JAR still bundles all four. build-helper attaches + # the staged classifier JARs. Gate already ran, so -DskipTests. + - name: Publish to Maven Central + if: ${{ github.event.inputs.skip_publish != 'true' }} + env: + IS_JAVA_8: true + run: | + mvn deploy -Prelease -DskipTests -DmultiArch=false \ + -s "$MAVEN_SETTINGS" \ + -Dgpg.keyname="$GPG_KEYNAME" -Dgpg.passphrase="$GPG_PASSPHRASE" \ + --file "$MODULE/pom.xml" + + - name: Tag and push (only after publish succeeds) + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + git commit -am "chore(ric): release ${EFFECTIVE_RELEASE_VERSION}" + git tag "$TAG_NAME" + mvn versions:set -DnewVersion="$NEXT_DEV_VERSION" -DgenerateBackupPoms=false --file "$MODULE/pom.xml" + git commit -am "chore(ric): prepare next development ${NEXT_DEV_VERSION}" + git push --atomic origin "HEAD:${GITHUB_REF_NAME}" "refs/tags/${TAG_NAME}" + + # Dry-run: validate assembly, no publish/push. + - name: Dry-run assemble (no publish) + if: ${{ github.event.inputs.skip_publish == 'true' }} + env: + IS_JAVA_8: true + run: mvn package -DskipTests -DmultiArch=false --file "$MODULE/pom.xml" + + # Nothing was pushed, so this only cleans the runner. + - name: Roll back local tag on failure + if: ${{ failure() && github.event.inputs.skip_publish != 'true' }} + run: | + git tag -d "$TAG_NAME" 2>/dev/null || true + echo "::warning::Release failed. The remote was not modified; safe to retry." + + - name: Summary + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + echo "## Release Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Module | \`$MODULE\` |" >> $GITHUB_STEP_SUMMARY + echo "| Version | \`$EFFECTIVE_RELEASE_VERSION\` |" >> $GITHUB_STEP_SUMMARY + echo "| Tag | \`$TAG_NAME\` |" >> $GITHUB_STEP_SUMMARY + echo "| Artifacts | main JAR + linux/linux_musl x x86_64/aarch_64 classifier JARs |" >> $GITHUB_STEP_SUMMARY + echo "| Built natively | x86_64 on ubuntu-latest, aarch_64 on ubuntu-24.04-arm (no QEMU) |" >> $GITHUB_STEP_SUMMARY + echo "| Maven Central | [com.amazonaws:$MODULE:$EFFECTIVE_RELEASE_VERSION](https://central.sonatype.com/artifact/com.amazonaws/$MODULE/$EFFECTIVE_RELEASE_VERSION) |" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d899990b..e148c0131 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,6 @@ name: Release to Maven Central -# Automated release pipeline: builds, tests, and publishes a module to Maven -# Central in one controlled environment. Signing/publishing secrets are still -# to be handled; until then use skip_publish (dry-run), which needs none. +# Builds, tests, and publishes a module to Maven Central in one environment. on: workflow_dispatch: @@ -11,12 +9,14 @@ on: description: 'Module to release (directory name, e.g. aws-lambda-java-log4j2)' required: true type: choice + # aws-lambda-java-runtime-interface-client is intentionally excluded: it + # ships a cross-compiled JNI native library and has its own dedicated + # pipeline, .github/workflows/release-runtime-interface-client.yml. options: - aws-lambda-java-core - aws-lambda-java-events - aws-lambda-java-events-sdk-transformer - aws-lambda-java-log4j2 - - aws-lambda-java-runtime-interface-client - aws-lambda-java-serialization - aws-lambda-java-tests releaseVersion: @@ -34,8 +34,8 @@ on: default: false permissions: - contents: write # push release commits and tag - id-token: write # reserved for future secrets handling + contents: write + id-token: write # Serialize all releases repo-wide to avoid concurrent pushes racing on the # default branch. Never cancel in-flight: it could leave a half-published state. @@ -49,6 +49,8 @@ env: DEVELOPMENT_VERSION_INPUT: ${{ github.event.inputs.developmentVersion }} # Batch mode + no transfer-progress spam for every Maven call (Maven 3.9+). MAVEN_ARGS: "-B --no-transfer-progress" + AWS_REGION: ${{ vars.AWS_REGION_MAVEN_RELEASE }} + OIDC_ROLE_ARN: ${{ secrets.AWS_ROLE_MAVEN_RELEASE }} jobs: release: @@ -111,19 +113,16 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - # TODO(secrets): signing/publishing credentials are still to be handled. - # Until then, only skip_publish=true (dry-run) works, as it needs none. - - name: Install intra-repo dependencies run: | - # Modules that must be installed locally before the target builds. + # Installed so the target compiles. -DskipTests: not released here, + # only the target module gets the full verify gate below. declare -A DEPS DEPS[aws-lambda-java-core]="" DEPS[aws-lambda-java-events]="" DEPS[aws-lambda-java-serialization]="" DEPS[aws-lambda-java-log4j2]="aws-lambda-java-core" DEPS[aws-lambda-java-events-sdk-transformer]="aws-lambda-java-events" - DEPS[aws-lambda-java-runtime-interface-client]="aws-lambda-java-core aws-lambda-java-serialization" DEPS[aws-lambda-java-tests]="aws-lambda-java-core aws-lambda-java-serialization aws-lambda-java-events" DEP_LIST="${DEPS[$MODULE]}" @@ -137,23 +136,72 @@ jobs: echo "::notice::No intra-repo dependencies for $MODULE" fi - # No skip-tests option: never release an unverified artifact. - name: Run tests run: mvn verify --file "$MODULE/pom.xml" - # prepare/perform aren't atomic. Prepare locally (no push), publish, then - # push only after the artifact is live, so a failed publish never leaves an - # orphan tag on the remote. On failure, the rollback step cleans the runner. + - name: Configure AWS credentials (OIDC) + if: ${{ github.event.inputs.skip_publish != 'true' }} + uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 + with: + aws-region: ${{ env.AWS_REGION }} + role-to-assume: ${{ env.OIDC_ROLE_ARN }} + role-session-name: GitHubActionsMavenCentralRelease + role-duration-seconds: 3600 + + - name: Fetch signing key and Sonatype credentials + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + # Shared secrets from LambdaMavenDeploy; nothing stored in GitHub. + GPG_JSON=$(aws secretsmanager get-secret-value --secret-id maven.gpg.keys --query SecretString --output text) + CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) + + GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") + GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") + SONATYPE_USERNAME=$(jq -r '."maven-central-login"' <<< "$CREDS_JSON") + SONATYPE_PASSWORD=$(jq -r '."maven-central-password"' <<< "$CREDS_JSON") + echo "::add-mask::$GPG_PASSPHRASE" + echo "::add-mask::$SONATYPE_USERNAME" + echo "::add-mask::$SONATYPE_PASSWORD" + + # Import the key with loopback pinentry so Maven can sign non-interactively. + GNUPGHOME=$(mktemp -d) + chmod 700 "$GNUPGHOME" + echo "allow-loopback-pinentry" > "$GNUPGHOME/gpg-agent.conf" + echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf" + export GNUPGHOME + gpgconf --kill gpg-agent || true + gpg --batch --import <<< "$GPG_PRIVATE_KEY" + GPG_KEYNAME=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}') + + # settings.xml with the Sonatype token (server id "central"). + SETTINGS="$RUNNER_TEMP/settings.xml" + { + echo '' + echo "central" + echo "${SONATYPE_USERNAME}" + echo "${SONATYPE_PASSWORD}" + echo '' + } > "$SETTINGS" + + # Pass to later steps (values already masked). + echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" + echo "GPG_KEYNAME=$GPG_KEYNAME" >> "$GITHUB_ENV" + echo "GPG_PASSPHRASE=$GPG_PASSPHRASE" >> "$GITHUB_ENV" + echo "MAVEN_SETTINGS=$SETTINGS" >> "$GITHUB_ENV" + + # prepare/perform aren't atomic: prepare locally, publish, push only after. - name: Release (prepare locally, publish, then push) if: ${{ github.event.inputs.skip_publish != 'true' }} run: | - # 1. Prepare locally (no push): create the release commits + tag. + # Prepare locally (no push): release commits + tag. mvn release:prepare -DpushChanges=false $RELEASE_ARGS --file "$MODULE/pom.xml" - # 2. Publish to Maven Central from the local tag. - mvn release:perform -DlocalCheckout=true --file "$MODULE/pom.xml" + # perform forks a fresh build, so pass settings/gpg via -Darguments. + mvn release:perform -DlocalCheckout=true \ + -Darguments="-s $MAVEN_SETTINGS -Prelease -Dgpg.keyname=$GPG_KEYNAME -Dgpg.passphrase=$GPG_PASSPHRASE" \ + --file "$MODULE/pom.xml" - # 3. Push commits and tag atomically (both or neither). + # Push commits + tag atomically, only after publish succeeded. git push --atomic origin \ "HEAD:${GITHUB_REF_NAME}" \ "refs/tags/${MODULE}-${EFFECTIVE_RELEASE_VERSION}" From f00150c1ef60696ee02041afbbd808b842a3bfbd Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 28 Jul 2026 15:58:21 +0100 Subject: [PATCH 4/5] refactor(release): reusable CI actions and review hardening - Extract resolve-release-version and configure-release-aws-credentials composite actions shared by both release workflows - Fetch signing key + Sonatype token inside the publish step so secrets never cross $GITHUB_ENV; scrub settings.xml and keyring on exit - Guard releases to the main branch; cut OIDC role session to 300s - Set RIC to 2.12.0-SNAPSHOT so the release pipeline can publish it --- .../action.yml | 27 ++++++ .../resolve-release-version/action.yml | 54 +++++++++++ .../release-runtime-interface-client.yml | 83 ++++++++--------- .github/workflows/release.yml | 93 +++++++++---------- .../pom.xml | 2 +- 5 files changed, 166 insertions(+), 93 deletions(-) create mode 100644 .github/actions/configure-release-aws-credentials/action.yml create mode 100644 .github/actions/resolve-release-version/action.yml diff --git a/.github/actions/configure-release-aws-credentials/action.yml b/.github/actions/configure-release-aws-credentials/action.yml new file mode 100644 index 000000000..a02a1e9a4 --- /dev/null +++ b/.github/actions/configure-release-aws-credentials/action.yml @@ -0,0 +1,27 @@ +name: "Configure AWS credentials for release (OIDC)" +description: > + Assumes the release OIDC role via aws-actions/configure-aws-credentials so the + job can read the signing key and Sonatype token from Secrets Manager. Pinning + of the underlying action lives here so it is updated in one place. + +inputs: + aws-region: + description: "AWS region to operate in." + required: true + role-to-assume: + description: "ARN of the OIDC role to assume." + required: true + role-session-name: + description: "Session name for the assumed role (helps distinguish callers in CloudTrail)." + required: true + +runs: + using: composite + steps: + - uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 + with: + aws-region: ${{ inputs.aws-region }} + role-to-assume: ${{ inputs.role-to-assume }} + role-session-name: ${{ inputs.role-session-name }} + # Short-lived: the job only needs the role briefly to read two secrets. + role-duration-seconds: 300 diff --git a/.github/actions/resolve-release-version/action.yml b/.github/actions/resolve-release-version/action.yml new file mode 100644 index 000000000..06d3f4f1e --- /dev/null +++ b/.github/actions/resolve-release-version/action.yml @@ -0,0 +1,54 @@ +name: "Resolve and validate release version" +description: > + Reads the module POM version (the source of truth), verifies it is a + -SNAPSHOT, and derives the effective release version (the optional override, + or the POM version with -SNAPSHOT stripped). Exports CURRENT_VERSION and + EFFECTIVE_RELEASE_VERSION to the job environment for subsequent steps. + +inputs: + module: + description: "Module directory containing the pom.xml to release." + required: true + release-version-override: + description: "Optional release version; defaults to the POM version without -SNAPSHOT." + required: false + default: "" + validate-module-dir: + description: "Fail if the module directory or its pom.xml is missing (use for the choice-driven workflow)." + required: false + default: "false" + +runs: + using: composite + steps: + - name: Resolve and validate release version + shell: bash + env: + MODULE: ${{ inputs.module }} + RELEASE_VERSION_OVERRIDE: ${{ inputs.release-version-override }} + VALIDATE_MODULE_DIR: ${{ inputs.validate-module-dir }} + run: | + if [[ "$VALIDATE_MODULE_DIR" == "true" ]]; then + if [[ ! -d "$MODULE" ]]; then + echo "::error::Module directory '$MODULE' does not exist" + exit 1 + fi + if [[ ! -f "$MODULE/pom.xml" ]]; then + echo "::error::No pom.xml found in '$MODULE'" + exit 1 + fi + fi + + # The POM version is the source of truth and must be a SNAPSHOT. + CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") + CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" + if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then + echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" + exit 1 + fi + + # Optional override; default strips -SNAPSHOT. + EFFECTIVE_RELEASE_VERSION="${RELEASE_VERSION_OVERRIDE:-${CURRENT_VERSION%-SNAPSHOT}}" + + echo "CURRENT_VERSION=$CURRENT_VERSION" >> "$GITHUB_ENV" + echo "EFFECTIVE_RELEASE_VERSION=$EFFECTIVE_RELEASE_VERSION" >> "$GITHUB_ENV" diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index 43090d1b7..6cc3bd6ec 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -57,6 +57,16 @@ jobs: runs-on: ${{ matrix.runner }} timeout-minutes: 45 steps: + # Manual (workflow_dispatch) releases must only run from main, never from + # an arbitrary branch that could carry unreviewed release logic. Guarding + # the first job blocks the whole pipeline (release needs build-natives). + - name: Verify release branch + run: | + if [[ "$GITHUB_REF_NAME" != "main" ]]; then + echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" + exit 1 + fi + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up JDK 8 @@ -66,15 +76,11 @@ jobs: distribution: corretto cache: maven - - name: Resolve release version - run: | - CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") - CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" - if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then - echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" - exit 1 - fi - echo "EFFECTIVE_RELEASE_VERSION=${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" >> "$GITHUB_ENV" + - name: Resolve and validate release version + uses: ./.github/actions/resolve-release-version + with: + module: ${{ env.MODULE }} + release-version-override: ${{ env.RELEASE_VERSION_INPUT }} # -DskipTests: only installed so the module compiles, not released here. - name: Install intra-repo dependencies @@ -124,17 +130,14 @@ jobs: distribution: corretto cache: maven - - name: Validate inputs and resolve versions - run: | - CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") - CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" - if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then - echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" - exit 1 - fi - - EFFECTIVE_RELEASE_VERSION="${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" + - name: Resolve and validate release version + uses: ./.github/actions/resolve-release-version + with: + module: ${{ env.MODULE }} + release-version-override: ${{ env.RELEASE_VERSION_INPUT }} + - name: Resolve next development version and tag + run: | # Next development version: use the override, or bump the patch. if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then if [[ "$DEVELOPMENT_VERSION_INPUT" != *-SNAPSHOT ]]; then @@ -147,7 +150,6 @@ jobs: NEXT_DEV_VERSION="${MA}.${MI}.$((PA + 1))-SNAPSHOT" fi - echo "EFFECTIVE_RELEASE_VERSION=$EFFECTIVE_RELEASE_VERSION" >> "$GITHUB_ENV" echo "NEXT_DEV_VERSION=$NEXT_DEV_VERSION" >> "$GITHUB_ENV" echo "TAG_NAME=${MODULE}-${EFFECTIVE_RELEASE_VERSION}" >> "$GITHUB_ENV" echo "::notice::Releasing $MODULE $EFFECTIVE_RELEASE_VERSION (next dev $NEXT_DEV_VERSION)" @@ -190,20 +192,32 @@ jobs: - name: Configure AWS credentials (OIDC) if: ${{ github.event.inputs.skip_publish != 'true' }} - uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 + uses: ./.github/actions/configure-release-aws-credentials with: aws-region: ${{ env.AWS_REGION }} role-to-assume: ${{ env.OIDC_ROLE_ARN }} role-session-name: GitHubActionsRicMavenCentralRelease - role-duration-seconds: 3600 - - name: Fetch signing key and Sonatype credentials + # Fetch signing material and publish in a single step so the GPG passphrase + # and Sonatype token stay in this shell and never cross a $GITHUB_ENV + # boundary, where a later (possibly compromised) step could read them. + # -DmultiArch=false builds only the host .so; the aarch_64 .so is already + # staged, so the main JAR still bundles all four. build-helper attaches + # the staged classifier JARs. Gate already ran, so -DskipTests. + - name: Publish to Maven Central if: ${{ github.event.inputs.skip_publish != 'true' }} + env: + IS_JAVA_8: true run: | - # Shared secrets from LambdaMavenDeploy; nothing stored in GitHub. + # Scrub the settings.xml (contains the Sonatype token) and the keyring + # on exit, so no sensitive file is left on the runner even on failure. + MAVEN_SETTINGS="$RUNNER_TEMP/settings.xml" + export GNUPGHOME=$(mktemp -d) + trap 'rm -rf "$MAVEN_SETTINGS" "$GNUPGHOME"' EXIT + + # --- Signing key + Sonatype token (shared secrets from LambdaMavenDeploy) --- GPG_JSON=$(aws secretsmanager get-secret-value --secret-id maven.gpg.keys --query SecretString --output text) CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) - GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") SONATYPE_USERNAME=$(jq -r '."maven-central-login"' <<< "$CREDS_JSON") @@ -213,38 +227,23 @@ jobs: echo "::add-mask::$SONATYPE_PASSWORD" # Import the key with loopback pinentry so Maven can sign non-interactively. - GNUPGHOME=$(mktemp -d) chmod 700 "$GNUPGHOME" echo "allow-loopback-pinentry" > "$GNUPGHOME/gpg-agent.conf" echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf" - export GNUPGHOME gpgconf --kill gpg-agent || true gpg --batch --import <<< "$GPG_PRIVATE_KEY" GPG_KEYNAME=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}') # settings.xml with the Sonatype token (server id "central"). - SETTINGS="$RUNNER_TEMP/settings.xml" { echo '' echo "central" echo "${SONATYPE_USERNAME}" echo "${SONATYPE_PASSWORD}" echo '' - } > "$SETTINGS" + } > "$MAVEN_SETTINGS" - echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" - echo "GPG_KEYNAME=$GPG_KEYNAME" >> "$GITHUB_ENV" - echo "GPG_PASSPHRASE=$GPG_PASSPHRASE" >> "$GITHUB_ENV" - echo "MAVEN_SETTINGS=$SETTINGS" >> "$GITHUB_ENV" - - # -DmultiArch=false builds only the host .so; the aarch_64 .so is already - # staged, so the main JAR still bundles all four. build-helper attaches - # the staged classifier JARs. Gate already ran, so -DskipTests. - - name: Publish to Maven Central - if: ${{ github.event.inputs.skip_publish != 'true' }} - env: - IS_JAVA_8: true - run: | + # --- Publish --- mvn deploy -Prelease -DskipTests -DmultiArch=false \ -s "$MAVEN_SETTINGS" \ -Dgpg.keyname="$GPG_KEYNAME" -Dgpg.passphrase="$GPG_PASSPHRASE" \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e148c0131..d9437b776 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,6 +59,15 @@ jobs: timeout-minutes: 30 steps: + # Manual (workflow_dispatch) releases must only run from main, never from + # an arbitrary branch that could carry unreviewed release logic. + - name: Verify release branch + run: | + if [[ "$GITHUB_REF_NAME" != "main" ]]; then + echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" + exit 1 + fi + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 # full history for tagging/pushing @@ -71,41 +80,19 @@ jobs: distribution: corretto cache: maven - - name: Validate inputs and resolve versions - run: | - if [[ ! -d "$MODULE" ]]; then - echo "::error::Module directory '$MODULE' does not exist" - exit 1 - fi - if [[ ! -f "$MODULE/pom.xml" ]]; then - echo "::error::No pom.xml found in '$MODULE'" - exit 1 - fi - - # The POM version is the source of truth and must be a SNAPSHOT. - CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") - CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" - if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then - echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" - exit 1 - fi - - # releaseVersion input is an optional override; default strips -SNAPSHOT. - EFFECTIVE_RELEASE_VERSION="${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" + - name: Resolve and validate release version + uses: ./.github/actions/resolve-release-version + with: + module: ${{ env.MODULE }} + release-version-override: ${{ env.RELEASE_VERSION_INPUT }} + validate-module-dir: "true" + - name: Validate development version override + run: | if [[ -n "$DEVELOPMENT_VERSION_INPUT" && "$DEVELOPMENT_VERSION_INPUT" != *-SNAPSHOT ]]; then echo "::error::developmentVersion '$DEVELOPMENT_VERSION_INPUT' must end with -SNAPSHOT" exit 1 fi - - # Build the release plugin version args once; reused by both paths. - RELEASE_ARGS="-DreleaseVersion=$EFFECTIVE_RELEASE_VERSION" - if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then - RELEASE_ARGS="$RELEASE_ARGS -DdevelopmentVersion=$DEVELOPMENT_VERSION_INPUT" - fi - - echo "EFFECTIVE_RELEASE_VERSION=$EFFECTIVE_RELEASE_VERSION" >> "$GITHUB_ENV" - echo "RELEASE_ARGS=$RELEASE_ARGS" >> "$GITHUB_ENV" echo "::notice::Releasing $MODULE $EFFECTIVE_RELEASE_VERSION (POM currently $CURRENT_VERSION)" - name: Configure git user @@ -141,20 +128,28 @@ jobs: - name: Configure AWS credentials (OIDC) if: ${{ github.event.inputs.skip_publish != 'true' }} - uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 + uses: ./.github/actions/configure-release-aws-credentials with: aws-region: ${{ env.AWS_REGION }} role-to-assume: ${{ env.OIDC_ROLE_ARN }} role-session-name: GitHubActionsMavenCentralRelease - role-duration-seconds: 3600 - - name: Fetch signing key and Sonatype credentials + # Fetch signing material and publish in a single step so the GPG passphrase + # and Sonatype token stay in this shell and never cross a $GITHUB_ENV + # boundary, where a later (possibly compromised) step could read them. + # prepare/perform aren't atomic: prepare locally, publish, push only after. + - name: Release (prepare locally, publish, then push) if: ${{ github.event.inputs.skip_publish != 'true' }} run: | - # Shared secrets from LambdaMavenDeploy; nothing stored in GitHub. + # Scrub the settings.xml (contains the Sonatype token) and the keyring + # on exit, so no sensitive file is left on the runner even on failure. + MAVEN_SETTINGS="$RUNNER_TEMP/settings.xml" + export GNUPGHOME=$(mktemp -d) + trap 'rm -rf "$MAVEN_SETTINGS" "$GNUPGHOME"' EXIT + + # --- Signing key + Sonatype token (shared secrets from LambdaMavenDeploy) --- GPG_JSON=$(aws secretsmanager get-secret-value --secret-id maven.gpg.keys --query SecretString --output text) CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) - GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") SONATYPE_USERNAME=$(jq -r '."maven-central-login"' <<< "$CREDS_JSON") @@ -164,37 +159,31 @@ jobs: echo "::add-mask::$SONATYPE_PASSWORD" # Import the key with loopback pinentry so Maven can sign non-interactively. - GNUPGHOME=$(mktemp -d) chmod 700 "$GNUPGHOME" echo "allow-loopback-pinentry" > "$GNUPGHOME/gpg-agent.conf" echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf" - export GNUPGHOME gpgconf --kill gpg-agent || true gpg --batch --import <<< "$GPG_PRIVATE_KEY" GPG_KEYNAME=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}') # settings.xml with the Sonatype token (server id "central"). - SETTINGS="$RUNNER_TEMP/settings.xml" { echo '' echo "central" echo "${SONATYPE_USERNAME}" echo "${SONATYPE_PASSWORD}" echo '' - } > "$SETTINGS" + } > "$MAVEN_SETTINGS" - # Pass to later steps (values already masked). - echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" - echo "GPG_KEYNAME=$GPG_KEYNAME" >> "$GITHUB_ENV" - echo "GPG_PASSPHRASE=$GPG_PASSPHRASE" >> "$GITHUB_ENV" - echo "MAVEN_SETTINGS=$SETTINGS" >> "$GITHUB_ENV" + # --- Release: build args as an array so each value is a single, + # properly quoted argument (no word-splitting of untrusted input). --- + RELEASE_ARGS=(-DreleaseVersion="$EFFECTIVE_RELEASE_VERSION") + if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then + RELEASE_ARGS+=(-DdevelopmentVersion="$DEVELOPMENT_VERSION_INPUT") + fi - # prepare/perform aren't atomic: prepare locally, publish, push only after. - - name: Release (prepare locally, publish, then push) - if: ${{ github.event.inputs.skip_publish != 'true' }} - run: | # Prepare locally (no push): release commits + tag. - mvn release:prepare -DpushChanges=false $RELEASE_ARGS --file "$MODULE/pom.xml" + mvn release:prepare -DpushChanges=false "${RELEASE_ARGS[@]}" --file "$MODULE/pom.xml" # perform forks a fresh build, so pass settings/gpg via -Darguments. mvn release:perform -DlocalCheckout=true \ @@ -209,7 +198,11 @@ jobs: - name: Dry-run release (prepare only, no publish) if: ${{ github.event.inputs.skip_publish == 'true' }} run: | - mvn release:prepare -DdryRun=true $RELEASE_ARGS --file "$MODULE/pom.xml" + RELEASE_ARGS=(-DreleaseVersion="$EFFECTIVE_RELEASE_VERSION") + if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then + RELEASE_ARGS+=(-DdevelopmentVersion="$DEVELOPMENT_VERSION_INPUT") + fi + mvn release:prepare -DdryRun=true "${RELEASE_ARGS[@]}" --file "$MODULE/pom.xml" mvn release:clean --file "$MODULE/pom.xml" || true # Nothing was pushed, so this only cleans the runner for a retry. diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 10a755c8c..a9c49c958 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.12.0 + 2.12.0-SNAPSHOT jar AWS Lambda Java Runtime Interface Client From b9418bc3e02c03fd149e4174dcc3d46ef643773d Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 28 Jul 2026 23:00:17 +0100 Subject: [PATCH 5/5] feat(release): gate publish on module-specific tests - log4j2 gates on the CloudWatch integration test (reusable workflow), bound to the publish event - serialization gates on the aws-lambda-java-tests suite (version-overridden), which its own mvn verify does not run --- .github/workflows/release.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d9437b776..2305ade7c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,20 @@ env: OIDC_ROLE_ARN: ${{ secrets.AWS_ROLE_MAVEN_RELEASE }} jobs: + # Pre-publish gate for log4j2: deploy a real Lambda, invoke it, + # and assert the log line reaches CloudWatch. Binds the end-to-end validation + # to the publish event itself. Skipped for every other module, which are + # covered by their own tests (or the cross-module gate below). + integration-test: + if: ${{ github.event.inputs.module == 'aws-lambda-java-log4j2' }} + uses: ./.github/workflows/run-integration-test.yml + secrets: inherit + release: + needs: [integration-test] + # Publish when the gate passed, or when it was skipped for a non-log4j2 + # module. A failed or cancelled gate blocks the release. + if: ${{ always() && (needs.integration-test.result == 'success' || needs.integration-test.result == 'skipped') }} runs-on: ubuntu-latest environment: Release timeout-minutes: 30 @@ -126,6 +139,28 @@ jobs: - name: Run tests run: mvn verify --file "$MODULE/pom.xml" + # Cross-module gate: serialization has no tests in its own build, so the + # `mvn verify` above exercises nothing. Its behavioral coverage lives in + # aws-lambda-java-tests, which depends on serialization via a version + # property. Install the just-built serialization and run that suite + # against it, so we never publish serialization the suite hasn't exercised. + - name: Run cross-module test gate + run: | + case "$MODULE" in + aws-lambda-java-serialization) + MOD_VER=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") + MOD_VER="${MOD_VER//[$'\r\n']/}" + echo "::group::Installing $MODULE $MOD_VER for the gate" + mvn install -DskipTests --file "$MODULE/pom.xml" + echo "::endgroup::" + echo "::notice::Gating $MODULE on aws-lambda-java-tests (aws-lambda-java-serialization.version=$MOD_VER)" + mvn verify -Daws-lambda-java-serialization.version="$MOD_VER" --file aws-lambda-java-tests/pom.xml + ;; + *) + echo "::notice::No cross-module test gate for $MODULE" + ;; + esac + - name: Configure AWS credentials (OIDC) if: ${{ github.event.inputs.skip_publish != 'true' }} uses: ./.github/actions/configure-release-aws-credentials