-
Notifications
You must be signed in to change notification settings - Fork 11
ci: publish Java OTel Lambda layer #621
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7555db1
cec4463
8a8556e
f05bab7
11cea0d
27c2681
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,367 @@ | ||||||||
| name: Publish OTel Plugin Lambda Layer | ||||||||
|
|
||||||||
| on: | ||||||||
| release: | ||||||||
| types: [published] | ||||||||
| workflow_dispatch: | ||||||||
| inputs: | ||||||||
| regions: | ||||||||
| description: "Comma-separated AWS Regions; defaults to all commercial Regions" | ||||||||
| required: false | ||||||||
| type: string | ||||||||
|
|
||||||||
| permissions: | ||||||||
| contents: read | ||||||||
|
|
||||||||
| concurrency: | ||||||||
| group: lambda-layer-publish-${{ github.sha }} | ||||||||
| cancel-in-progress: false | ||||||||
|
|
||||||||
| env: | ||||||||
| LAYER_NAME: aws-durable-execution-sdk-java-otel-plugin | ||||||||
| SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && github.sha || github.event.release.tag_name }} | ||||||||
| DEFAULT_LAYER_REGIONS: >- | ||||||||
| af-south-1, | ||||||||
| ap-east-1, | ||||||||
| ap-east-2, | ||||||||
| ap-northeast-1, | ||||||||
| ap-northeast-2, | ||||||||
| ap-northeast-3, | ||||||||
| ap-south-1, | ||||||||
| ap-south-2, | ||||||||
| ap-southeast-1, | ||||||||
| ap-southeast-2, | ||||||||
| ap-southeast-3, | ||||||||
| ap-southeast-4, | ||||||||
| ap-southeast-5, | ||||||||
| ap-southeast-6, | ||||||||
| ap-southeast-7, | ||||||||
| ca-central-1, | ||||||||
| ca-west-1, | ||||||||
| eu-central-1, | ||||||||
| eu-central-2, | ||||||||
| eu-north-1, | ||||||||
| eu-south-1, | ||||||||
| eu-south-2, | ||||||||
| eu-west-1, | ||||||||
| eu-west-2, | ||||||||
| eu-west-3, | ||||||||
| il-central-1, | ||||||||
| me-central-1, | ||||||||
| me-south-1, | ||||||||
| mx-central-1, | ||||||||
| sa-east-1, | ||||||||
| us-east-1, | ||||||||
| us-east-2, | ||||||||
| us-west-1, | ||||||||
| us-west-2 | ||||||||
|
|
||||||||
| jobs: | ||||||||
| build-layer: | ||||||||
| if: >- | ||||||||
| github.event_name == 'release' || | ||||||||
| (github.event_name == 'workflow_dispatch' && | ||||||||
| github.ref == format('refs/heads/{0}', github.event.repository.default_branch)) | ||||||||
| runs-on: ubuntu-latest | ||||||||
| outputs: | ||||||||
| sdk_version: ${{ steps.version.outputs.sdk_version }} | ||||||||
| source_sha: ${{ steps.source.outputs.sha }} | ||||||||
|
|
||||||||
| steps: | ||||||||
| - name: Checkout source | ||||||||
| uses: actions/checkout@v7 | ||||||||
| with: | ||||||||
| fetch-depth: 0 | ||||||||
| ref: ${{ env.SOURCE_REF }} | ||||||||
|
|
||||||||
| - name: Record source revision | ||||||||
| id: source | ||||||||
| shell: bash | ||||||||
| run: | | ||||||||
| set -euo pipefail | ||||||||
|
|
||||||||
| echo "epoch=$(git show -s --format=%ct HEAD)" >> "$GITHUB_OUTPUT" | ||||||||
| echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" | ||||||||
|
|
||||||||
| - name: Setup Java | ||||||||
| uses: actions/setup-java@v5 | ||||||||
| with: | ||||||||
| distribution: corretto | ||||||||
| java-version: '17' | ||||||||
| cache: maven | ||||||||
|
|
||||||||
| - name: Determine SDK version | ||||||||
| id: version | ||||||||
| env: | ||||||||
| RELEASE_TAG: ${{ github.event.release.tag_name }} | ||||||||
| shell: bash | ||||||||
| run: | | ||||||||
| set -euo pipefail | ||||||||
|
|
||||||||
| sdk_version="$(mvn -q help:evaluate -Dexpression=project.version -DforceStdout)" | ||||||||
| if [ "$GITHUB_EVENT_NAME" = "release" ]; then | ||||||||
| if [[ ! "$RELEASE_TAG" =~ ^v([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?)$ ]]; then | ||||||||
| echo "Release tag must be a semantic version prefixed with v (for example, v1.2.0)." >&2 | ||||||||
| exit 1 | ||||||||
| fi | ||||||||
| if [ "$sdk_version" != "${BASH_REMATCH[1]}" ]; then | ||||||||
| echo "Release tag $RELEASE_TAG points to Maven version $sdk_version." >&2 | ||||||||
| exit 1 | ||||||||
| fi | ||||||||
| fi | ||||||||
|
|
||||||||
| echo "sdk_version=$sdk_version" >> "$GITHUB_OUTPUT" | ||||||||
|
|
||||||||
| - name: Verify release source | ||||||||
| if: github.event_name == 'release' | ||||||||
| env: | ||||||||
| BASE_BRANCH: ${{ github.event.repository.default_branch }} | ||||||||
| RELEASE_TAG: ${{ github.event.release.tag_name }} | ||||||||
| shell: bash | ||||||||
| run: | | ||||||||
| set -euo pipefail | ||||||||
|
|
||||||||
| git fetch origin "$BASE_BRANCH" | ||||||||
| if ! git merge-base --is-ancestor "${RELEASE_TAG}^{commit}" "origin/${BASE_BRANCH}"; then | ||||||||
| echo "Release tag $RELEASE_TAG does not point to a commit on $BASE_BRANCH." >&2 | ||||||||
| exit 1 | ||||||||
| fi | ||||||||
|
|
||||||||
| - name: Build layer zip | ||||||||
| id: build-layer | ||||||||
| env: | ||||||||
| LAYER_ZIP: dist/${{ env.LAYER_NAME }}.zip | ||||||||
| SDK_VERSION: ${{ steps.version.outputs.sdk_version }} | ||||||||
| SOURCE_DATE_EPOCH: ${{ steps.source.outputs.epoch }} | ||||||||
| SOURCE_SHA: ${{ steps.source.outputs.sha }} | ||||||||
| TZ: UTC | ||||||||
| shell: bash | ||||||||
| run: | | ||||||||
| set -euo pipefail | ||||||||
|
|
||||||||
| build_layer() { | ||||||||
| local output_zip=$1 | ||||||||
|
|
||||||||
| mvn -B -q -pl otel-plugin -am clean package \ | ||||||||
| -DskipTests \ | ||||||||
| -Dproject.build.outputTimestamp="$SOURCE_DATE_EPOCH" \ | ||||||||
| --no-transfer-progress | ||||||||
| rm -rf layer | ||||||||
| rm -f "$output_zip" | ||||||||
| mkdir -p layer/java/lib "$(dirname "$output_zip")" | ||||||||
| cp "otel-plugin/target/aws-durable-execution-sdk-java-plugin-otel-${SDK_VERSION}.jar" \ | ||||||||
| layer/java/lib/ | ||||||||
| cp LICENSE NOTICE layer/ | ||||||||
| find layer -exec touch -d "@${SOURCE_DATE_EPOCH}" {} + | ||||||||
| ( | ||||||||
| cd layer | ||||||||
| find . -type f -print | LC_ALL=C sort | zip -X -q "$GITHUB_WORKSPACE/$output_zip" -@ | ||||||||
| ) | ||||||||
| } | ||||||||
|
|
||||||||
| rebuild_zip="${LAYER_ZIP%.zip}-rebuild.zip" | ||||||||
| build_layer "$LAYER_ZIP" | ||||||||
| build_layer "$rebuild_zip" | ||||||||
| if ! cmp --silent "$LAYER_ZIP" "$rebuild_zip"; then | ||||||||
| echo "Layer builds for source $SOURCE_SHA are not reproducible." >&2 | ||||||||
| exit 1 | ||||||||
| fi | ||||||||
| rm "$rebuild_zip" | ||||||||
| echo "layer_zip=$LAYER_ZIP" >> "$GITHUB_OUTPUT" | ||||||||
|
|
||||||||
| - name: Verify layer contents | ||||||||
| env: | ||||||||
| LAYER_ZIP: ${{ steps.build-layer.outputs.layer_zip }} | ||||||||
| SDK_VERSION: ${{ steps.version.outputs.sdk_version }} | ||||||||
| shell: bash | ||||||||
| run: | | ||||||||
| set -euo pipefail | ||||||||
|
|
||||||||
| unzip -Z1 "$LAYER_ZIP" > layer-contents.txt | ||||||||
| grep -Fx "java/lib/aws-durable-execution-sdk-java-plugin-otel-${SDK_VERSION}.jar" layer-contents.txt | ||||||||
| grep -Fx "LICENSE" layer-contents.txt | ||||||||
| grep -Fx "NOTICE" layer-contents.txt | ||||||||
| if [ "$(grep -Ec '^java/lib/[^/]+\.jar$' layer-contents.txt)" -ne 1 ]; then | ||||||||
| echo "The layer must contain only the OTel plugin JAR." >&2 | ||||||||
| exit 1 | ||||||||
| fi | ||||||||
|
|
||||||||
| - name: Upload layer artifact | ||||||||
| uses: actions/upload-artifact@v7 | ||||||||
| with: | ||||||||
| name: otel-plugin-layer | ||||||||
| path: ${{ steps.build-layer.outputs.layer_zip }} | ||||||||
| if-no-files-found: error | ||||||||
| retention-days: 30 | ||||||||
|
|
||||||||
| publish-layer: | ||||||||
| needs: build-layer | ||||||||
| runs-on: ubuntu-latest | ||||||||
| environment: | ||||||||
| name: lambda-layer-publish | ||||||||
| permissions: | ||||||||
| contents: read | ||||||||
| id-token: write | ||||||||
| env: | ||||||||
| LAYER_REGIONS: ${{ inputs.regions || vars.LAYER_PUBLISH_REGIONS }} | ||||||||
|
|
||||||||
| steps: | ||||||||
| - name: Download layer artifact | ||||||||
| uses: actions/download-artifact@v8 | ||||||||
| with: | ||||||||
| name: otel-plugin-layer | ||||||||
| path: dist/ | ||||||||
|
|
||||||||
| - name: Configure AWS credentials | ||||||||
| uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 | ||||||||
| with: | ||||||||
| role-to-assume: ${{ secrets.LAYER_PUBLISH_ROLE_ARN }} | ||||||||
| role-session-name: otelLayerPublish | ||||||||
| aws-region: us-east-1 | ||||||||
|
|
||||||||
| - name: Publish layer versions | ||||||||
| env: | ||||||||
| LAYER_ZIP: dist/${{ env.LAYER_NAME }}.zip | ||||||||
| SDK_VERSION: ${{ needs.build-layer.outputs.sdk_version }} | ||||||||
| SOURCE_SHA: ${{ needs.build-layer.outputs.source_sha }} | ||||||||
| shell: bash | ||||||||
| run: | | ||||||||
| set -euo pipefail | ||||||||
|
|
||||||||
| PUBLISHED=false | ||||||||
| FAILED_REGIONS=() | ||||||||
| LOCAL_CODE_SHA256=$(openssl dgst -sha256 -binary "$LAYER_ZIP" | openssl base64 -A) | ||||||||
| REGION_LIST=${LAYER_REGIONS:-$DEFAULT_LAYER_REGIONS} | ||||||||
| IFS=',' read -ra REGIONS <<< "$REGION_LIST" | ||||||||
| for REGION in "${REGIONS[@]}"; do | ||||||||
| REGION=$(echo "$REGION" | xargs) | ||||||||
| if [ -z "$REGION" ]; then | ||||||||
| continue | ||||||||
| fi | ||||||||
|
|
||||||||
| LAYER_DESCRIPTION="AWS Durable Execution SDK for Java ${SDK_VERSION} OTel plugin source:${SOURCE_SHA}" | ||||||||
| if ! EXISTING_RESULT=$(aws lambda list-layer-versions \ | ||||||||
| --layer-name "$LAYER_NAME" \ | ||||||||
| --region "$REGION" \ | ||||||||
| --query "LayerVersions[?Description=='${LAYER_DESCRIPTION}'].[LayerVersionArn,Version]" \ | ||||||||
| --output text 2>&1); then | ||||||||
| if [[ "$EXISTING_RESULT" == *"ResourceNotFoundException"* ]]; then | ||||||||
| EXISTING_RESULT="" | ||||||||
| else | ||||||||
| echo "$EXISTING_RESULT" >&2 | ||||||||
| echo "::warning::Failed to list existing layer versions in ${REGION}" | ||||||||
| FAILED_REGIONS+=("$REGION") | ||||||||
| continue | ||||||||
| fi | ||||||||
| fi | ||||||||
|
|
||||||||
| if [ -n "$EXISTING_RESULT" ]; then | ||||||||
| read -r LAYER_VERSION_ARN VERSION_NUMBER <<< "$EXISTING_RESULT" | ||||||||
| if ! EXISTING_CODE_SHA256=$(aws lambda get-layer-version \ | ||||||||
| --layer-name "$LAYER_NAME" \ | ||||||||
| --version-number "$VERSION_NUMBER" \ | ||||||||
| --region "$REGION" \ | ||||||||
| --query 'Content.CodeSha256' \ | ||||||||
| --output text 2>&1); then | ||||||||
| echo "$EXISTING_CODE_SHA256" >&2 | ||||||||
| echo "::warning::Failed to read ${LAYER_VERSION_ARN}" | ||||||||
| FAILED_REGIONS+=("$REGION") | ||||||||
| continue | ||||||||
| fi | ||||||||
| if [ "$EXISTING_CODE_SHA256" != "$LOCAL_CODE_SHA256" ]; then | ||||||||
| echo "::error::Artifact hash mismatch for ${LAYER_VERSION_ARN}" | ||||||||
| FAILED_REGIONS+=("$REGION") | ||||||||
| continue | ||||||||
| fi | ||||||||
|
zhongkechen marked this conversation as resolved.
|
||||||||
| echo "Reusing ${LAYER_VERSION_ARN}" | ||||||||
| else | ||||||||
| if ! PUBLISH_RESULT=$(aws lambda publish-layer-version \ | ||||||||
| --layer-name "$LAYER_NAME" \ | ||||||||
| --description "$LAYER_DESCRIPTION" \ | ||||||||
| --zip-file "fileb://${LAYER_ZIP}" \ | ||||||||
| --compatible-runtimes java17 java21 java25 \ | ||||||||
| --compatible-architectures x86_64 arm64 \ | ||||||||
| --license-info Apache-2.0 \ | ||||||||
| --region "$REGION" \ | ||||||||
| --query '[LayerVersionArn,Version,Content.CodeSha256]' \ | ||||||||
| --output text); then | ||||||||
| echo "::warning::Failed to publish the layer in ${REGION}" | ||||||||
| FAILED_REGIONS+=("$REGION") | ||||||||
| continue | ||||||||
| fi | ||||||||
| read -r LAYER_VERSION_ARN VERSION_NUMBER PUBLISHED_CODE_SHA256 <<< "$PUBLISH_RESULT" | ||||||||
| if [ "$PUBLISHED_CODE_SHA256" != "$LOCAL_CODE_SHA256" ]; then | ||||||||
| echo "::error::Published artifact hash mismatch for ${LAYER_VERSION_ARN}" | ||||||||
| FAILED_REGIONS+=("$REGION") | ||||||||
| continue | ||||||||
| fi | ||||||||
| fi | ||||||||
|
|
||||||||
| if ! PERMISSION_RESULT=$(aws lambda add-layer-version-permission \ | ||||||||
| --layer-name "$LAYER_NAME" \ | ||||||||
| --version-number "$VERSION_NUMBER" \ | ||||||||
| --statement-id public-layer-access \ | ||||||||
| --action lambda:GetLayerVersion \ | ||||||||
| --principal "*" \ | ||||||||
| --region "$REGION" \ | ||||||||
| 2>&1); then | ||||||||
| if [[ "$PERMISSION_RESULT" != *"ResourceConflictException"* ]]; then | ||||||||
| echo "$PERMISSION_RESULT" >&2 | ||||||||
| echo "::warning::Failed to grant public access to ${LAYER_VERSION_ARN}" | ||||||||
| FAILED_REGIONS+=("$REGION") | ||||||||
| continue | ||||||||
| fi | ||||||||
|
zhongkechen marked this conversation as resolved.
|
||||||||
| fi | ||||||||
|
|
||||||||
| if ! POLICY_RESULT=$(aws lambda get-layer-version-policy \ | ||||||||
| --layer-name "$LAYER_NAME" \ | ||||||||
| --version-number "$VERSION_NUMBER" \ | ||||||||
| --region "$REGION" \ | ||||||||
| --query Policy \ | ||||||||
| --output text 2>&1); then | ||||||||
| echo "$POLICY_RESULT" >&2 | ||||||||
| echo "::warning::Failed to read the access policy for ${LAYER_VERSION_ARN}" | ||||||||
| FAILED_REGIONS+=("$REGION") | ||||||||
| continue | ||||||||
| fi | ||||||||
| if ! jq -e ' | ||||||||
| .Statement | ||||||||
| | if type == "array" then . else [.] end | ||||||||
| | any(.[]; | ||||||||
| .Sid == "public-layer-access" | ||||||||
| and .Effect == "Allow" | ||||||||
| and ( | ||||||||
| .Action == "lambda:GetLayerVersion" | ||||||||
| or ( | ||||||||
| (.Action | type) == "array" | ||||||||
| and (.Action | index("lambda:GetLayerVersion")) != null | ||||||||
| ) | ||||||||
| ) | ||||||||
| and ( | ||||||||
| .Principal == "*" | ||||||||
| or ( | ||||||||
| (.Principal | type) == "object" | ||||||||
| and .Principal.AWS == "*" | ||||||||
| ) | ||||||||
| ) | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Codex AI review [P2] Reject conditioned policies as non-public. An organization-scoped layer permission also has
Suggested change
|
||||||||
| ) | ||||||||
| ' <<< "$POLICY_RESULT" > /dev/null; then | ||||||||
| echo "::error::Expected public access policy is missing for ${LAYER_VERSION_ARN}" | ||||||||
| FAILED_REGIONS+=("$REGION") | ||||||||
| continue | ||||||||
| fi | ||||||||
|
|
||||||||
| echo "Available ${LAYER_VERSION_ARN}" | ||||||||
| echo "- \`${LAYER_VERSION_ARN}\`" >> "$GITHUB_STEP_SUMMARY" | ||||||||
| PUBLISHED=true | ||||||||
| done | ||||||||
|
|
||||||||
| if [ "${#FAILED_REGIONS[@]}" -gt 0 ]; then | ||||||||
| echo "::error::Layer publishing failed in: ${FAILED_REGIONS[*]}" | ||||||||
| exit 1 | ||||||||
| fi | ||||||||
|
|
||||||||
| if [ "$PUBLISHED" != true ]; then | ||||||||
| echo "No AWS regions were configured for layer publishing." | ||||||||
| exit 1 | ||||||||
| fi | ||||||||
Uh oh!
There was an error while loading. Please reload this page.