From 52bd30d7415eba7b6012c19e4c17cf4f2dfb53dc Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 14 Jul 2026 17:09:13 +0100 Subject: [PATCH 1/7] 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/7] 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/7] 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/7] 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/7] 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 From 8fbec4a175c148f4a736a4a262d890359bd52239 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Thu, 30 Jul 2026 15:39:46 +0100 Subject: [PATCH 6/7] fix(ci): add workflow_call trigger to integration test release.yml invokes run-integration-test.yml as a reusable workflow via uses:, which GitHub requires the called workflow to declare with an on: workflow_call trigger. Without it the log4j2 release would fail when resolving the reusable workflow. --- .github/workflows/run-integration-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index ad46d7fd6..35456a7c5 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -9,6 +9,7 @@ permissions: on: workflow_dispatch: + workflow_call: push: branches: [ main ] paths: From 12e36128374a747c697757083665587625b3d60b Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 11 Aug 2026 14:25:14 +0100 Subject: [PATCH 7/7] ci: update release signing key source and native build runners Point the Maven Central release workflows at the dedicated GPG signing key secret, and run the RIC native builds on the self-hosted release runners instead of the default hosted runners. --- .github/workflows/release-runtime-interface-client.yml | 6 +++--- .github/workflows/release.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index 6cc3bd6ec..90858d0f9 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -49,10 +49,10 @@ jobs: matrix: include: - arch: x86_64 - runner: ubuntu-latest + runner: codebuild-aws-lambda-java-libs-test-trigger-x86-${{ github.run_id }}-${{ github.run_attempt }} profiles: linux-x86_64 linux_musl-x86_64 - arch: aarch64 - runner: ubuntu-24.04-arm + runner: codebuild-aws-lambda-java-libs-test-trigger-arm64-${{ github.run_id }}-${{ github.run_attempt }} profiles: linux-aarch64 linux_musl-aarch64 runs-on: ${{ matrix.runner }} timeout-minutes: 45 @@ -216,7 +216,7 @@ jobs: 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) + GPG_JSON=$(aws secretsmanager get-secret-value --secret-id lambda-runtimes/java/gpg-signing-key --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") diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2305ade7c..a08d3b74a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -183,7 +183,7 @@ jobs: 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) + GPG_JSON=$(aws secretsmanager get-secret-value --secret-id lambda-runtimes/java/gpg-signing-key --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")