Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
367 changes: 367 additions & 0 deletions .github/workflows/lambda-layer-publish.yml
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
Comment thread
zhongkechen marked this conversation as resolved.
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
Comment thread
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
Comment thread
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 == "*"
)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 Principal: "*", but adds a restrictive Condition. After a statement-ID conflict, this check would accept that policy and report the layer as publicly available even though external accounts cannot use it. Require the matching statement to have no condition, and fail or replace restricted statements.

Suggested change
)
)
and (.Condition == null)

)
' <<< "$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
Loading