Release public Lambda layer #223
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Release public Lambda layer | |
on: | |
workflow_dispatch: | |
inputs: | |
layer_arn_keyword: | |
description: 'Clone layer from keyword ARN by substituting architecture parameter (ie: arn:aws:lambda:us-east-1:012345678912:layer:aws-otel-nodejs-wrapper-<ARCHITECTURE>-d8b20954c3284abcd08f5fb8178986a89cff7160:1)' | |
required: true | |
layer_name_keyword: | |
description: 'Publish layer to keyword name by substituting architecture parameter (ie: aws-otel-python-<ARCHITECTURE>-ver-1-7-1)' | |
required: true | |
aws_region: | |
description: 'Deploy to aws region' | |
required: true | |
default: "[ \"us-east-1\", \"us-east-2\", \"us-west-1\", \"us-west-2\", \"ap-south-1\", \"ap-northeast-2\", \"ap-southeast-1\", \"ap-southeast-2\", \"ap-northeast-1\", \"ca-central-1\", \"eu-central-1\", \"eu-west-1\", \"eu-west-2\", \"eu-west-3\", \"eu-north-1\", \"sa-east-1\" ]" | |
architecture: | |
# NOTE: (NathanielRN) The operator should be extra cautious if they are | |
# trying to modify the release "architecture" input, because the only | |
# reason they would is if the Soak Test Layer ARN Keyword only worked | |
# for ONE architecture and FAILED for the other and at the point we | |
# should be really certain that we want to release such a layer. | |
description: 'Architecture-compatible distributions of the layer to release in each region' | |
required: true | |
default: "[ \"amd64\", \"arm64\" ]" | |
env: | |
AMP_REGIONS: us-west-2,us-east-1,us-east-2,eu-central-1,eu-west-1 | |
permissions: | |
id-token: write | |
contents: read | |
jobs: | |
validate-inputs: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Validate `layer_name_keyword` (${{ github.event.inputs.layer_name_keyword }}) | |
run: | | |
grep -Eq "aws-otel-(collector|java-agent|java-wrapper|nodejs|python)-<ARCHITECTURE>-ver-[0-9]+-[0-9]+-[0-9]+" <<< "${{ github.event.inputs.layer_name_keyword }}" | |
publish-prod: | |
runs-on: ubuntu-latest | |
needs: validate-inputs | |
strategy: | |
matrix: | |
architecture: ${{ fromJson(github.event.inputs.architecture) }} | |
aws_region: ${{ fromJson(github.event.inputs.aws_region) }} | |
steps: | |
- uses: aws-actions/[email protected] | |
with: | |
role-to-assume: ${{ secrets.INTEG_TEST_LAMBDA_ROLE_ARN }} | |
role-duration-seconds: 1200 | |
aws-region: us-east-1 | |
- name: Get layer ARN by substituting `${{ matrix.architecture }}` into Soak Test ARN keyword | |
run: | | |
echo LAYER_ARN=$(echo "${{ github.event.inputs.layer_arn_keyword }}" | sed 's/<ARCHITECTURE>/${{ matrix.architecture }}/') | tee --append $GITHUB_ENV | |
- name: Get layer name by substituting `${{ matrix.architecture }}` into Workflow Input name keyword | |
run: | | |
echo LAYER_NAME=$(echo "${{ github.event.inputs.layer_name_keyword }}" | sed 's/<ARCHITECTURE>/${{ matrix.architecture }}/') | tee --append $GITHUB_ENV | |
- name: Get layer kind by parsing `${{ github.event.inputs.layer_name_keyword }}` | |
run: | | |
echo LAYER_KIND=$(echo "${{ github.event.inputs.layer_name_keyword }}" | cut -d - -f 3) | tee --append $GITHUB_ENV | |
- name: download layer from source | |
run: | | |
URL=$(aws lambda get-layer-version-by-arn --arn ${{ env.LAYER_ARN }} --query Content.Location --output text) | |
curl $URL -o layer.zip | |
# switch to prod | |
- uses: aws-actions/[email protected] | |
with: | |
role-to-assume: ${{ secrets.PROD_LAMBDA_ROLE_ARN }} | |
role-duration-seconds: 1200 | |
aws-region: ${{ matrix.aws_region }} | |
mask-aws-account-id: false | |
- name: Get bucket name for release run | |
run: | | |
echo BUCKET_NAME=publish-${{ env.LAYER_KIND }}-${{ matrix.architecture }}-${{ github.run_id }}-${{ matrix.aws_region }} | tee --append $GITHUB_ENV | |
- name: publish | |
run: | | |
aws s3 mb s3://${{ env.BUCKET_NAME }} | |
aws s3 cp layer.zip s3://${{ env.BUCKET_NAME }} | |
layerARN=$( | |
aws lambda publish-layer-version \ | |
--layer-name ${{ env.LAYER_NAME }} \ | |
--content S3Bucket=${{ env.BUCKET_NAME }},S3Key=layer.zip \ | |
--query 'LayerVersionArn' \ | |
--output text | |
) | |
echo $layerARN | |
mkdir ${{ env.LAYER_NAME }} | |
echo $layerARN > ${{ env.LAYER_NAME }}/${{ matrix.aws_region }} | |
cat ${{ env.LAYER_NAME }}/${{ matrix.aws_region }} | |
- name: public layer | |
run: | | |
layerVersion=$( | |
aws lambda list-layer-versions \ | |
--layer-name ${{ env.LAYER_NAME }} \ | |
--query 'max_by(LayerVersions, &Version).Version' | |
) | |
aws lambda add-layer-version-permission \ | |
--layer-name ${{ env.LAYER_NAME }} \ | |
--version-number $layerVersion \ | |
--principal "*" \ | |
--statement-id publish \ | |
--action lambda:GetLayerVersion | |
- name: upload layer arn artifact | |
if: ${{ success() }} | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ env.LAYER_NAME }} | |
path: ${{ env.LAYER_NAME }}/${{ matrix.aws_region }} | |
- name: clean s3 | |
if: always() | |
run: | | |
aws s3 rb --force s3://${{ env.BUCKET_NAME }} | |
generate-note: | |
runs-on: ubuntu-latest | |
needs: publish-prod | |
strategy: | |
matrix: | |
architecture: ${{ fromJson(github.event.inputs.architecture) }} | |
steps: | |
- uses: hashicorp/setup-terraform@v2 | |
- name: Get layer name by substituting `${{ matrix.architecture }}` into Workflow Input name keyword | |
run: | | |
echo LAYER_NAME=$(echo "${{ github.event.inputs.layer_name_keyword }}" | sed 's/<ARCHITECTURE>/${{ matrix.architecture }}/') | tee --append $GITHUB_ENV | |
- name: Get layer kind by parsing `${{ github.event.inputs.layer_name_keyword }}` | |
run: | | |
echo LAYER_KIND=$(echo "${{ github.event.inputs.layer_name_keyword }}" | cut -d - -f 3) | tee --append $GITHUB_ENV | |
- name: download layerARNs | |
uses: actions/download-artifact@v4 | |
with: | |
name: ${{ env.LAYER_NAME }} | |
path: ${{ env.LAYER_NAME }} | |
- name: show layerARNs | |
run: | | |
for file in ${{ env.LAYER_NAME }}/* | |
do | |
echo $file | |
cat $file | |
done | |
- name: generate layer-note | |
working-directory: ${{ env.LAYER_NAME }} | |
run: | | |
echo "| Region | Layer ARN |" >> ../layer-note | |
echo "| ---- | ---- |" >> ../layer-note | |
for file in * | |
do | |
read arn < $file | |
echo "| " $file " | " $arn " |" >> ../layer-note | |
done | |
cd .. | |
cat layer-note | |
- name: generate tf layer | |
working-directory: ${{ env.LAYER_NAME }} | |
run: | | |
echo "locals {" >> ../layer.tf | |
if [ "${{ env.LAYER_KIND }}" != 'collector' ] | |
then | |
echo " sdk_layer_arns_${{ matrix.architecture }} = {" >> ../layer.tf | |
else | |
echo " collector_layer_arns_${{ matrix.architecture }} = {" >> ../layer.tf | |
fi | |
for file in * | |
do | |
read arn < $file | |
echo " \""$file"\" = \""$arn"\"" >> ../layer.tf | |
done | |
cd .. | |
echo " }" >> layer.tf | |
echo "}" >> layer.tf | |
terraform fmt layer.tf | |
cat layer.tf | |
- name: upload layer tf file | |
uses: actions/upload-artifact@v4 | |
with: | |
name: layer_${{ matrix.architecture }}.tf | |
path: layer.tf | |
smoke-test: | |
name: Smoke Test - (${{ matrix.aws_region }} - ${{ github.event.inputs.layer_name_keyword }} - ${{ matrix.architecture }}) | |
needs: generate-note | |
runs-on: ubuntu-latest | |
strategy: | |
matrix: | |
architecture: ${{ fromJson(github.event.inputs.architecture) }} | |
aws_region: ${{ fromJson(github.event.inputs.aws_region) }} | |
steps: | |
- name: Parse smoke test values from layer name - ${{ github.event.inputs.layer_name_keyword }} | |
# FIXME: (enowell) You can only Smoke Test 1 Sample App with this | |
# design. However, there are multiple Sample Apps that test the same | |
# Java Wrapper Lambda Layer. | |
# | |
# i.e. | |
# - `aws-sdk` and `okhttp` both test the `java-wrapper` layer but we | |
# can only test one (`aws-sdk`). | |
# - `go` and `dotnet` both test the `collector` layer but we can only | |
# test one (`go`). | |
run: | | |
LAYER_KIND=$(echo "${{ github.event.inputs.layer_name_keyword }}" | cut -d - -f 3) | |
if [ "$LAYER_KIND" = "python" ]; then | |
TEST_LANGUAGE=python | |
elif [ "$LAYER_KIND" = "collector" ]; then | |
TEST_LANGUAGE=go | |
else | |
TEST_LANGUAGE=$LAYER_KIND | |
fi | |
echo TEST_LANGUAGE=$TEST_LANGUAGE | tee --append $GITHUB_ENV | |
LAYER_INSTR_TYPE=$(echo "${{ github.event.inputs.layer_name_keyword }}" | cut -d - -f 4) | |
if [ "$LAYER_INSTR_TYPE" = "agent" ] || [ "$LAYER_INSTR_TYPE" = "wrapper" ]; then | |
TEST_INSTR_TYPE=$LAYER_INSTR_TYPE | |
else | |
TEST_INSTR_TYPE="wrapper" | |
fi | |
echo TEST_INSTR_TYPE=$TEST_INSTR_TYPE | tee --append $GITHUB_ENV | |
- uses: actions/checkout@v4 | |
with: | |
submodules: recursive | |
- uses: actions/setup-java@v4 | |
if: ${{ env.TEST_LANGUAGE == 'java' }} | |
with: | |
distribution: corretto | |
java-version: '17' | |
- name: Cache (Java) | |
uses: actions/cache@v3 | |
if: ${{ env.TEST_LANGUAGE == 'java' }} | |
with: | |
path: | | |
~/go/pkg/mod | |
~/.gradle/caches | |
~/.gradle/wrapper | |
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}-go-${{ hashFiles('**/go.sum') }} | |
restore-keys: | | |
${{ runner.os }}-gradle- | |
- uses: actions/setup-node@v4 | |
if: ${{ env.TEST_LANGUAGE == 'nodejs' }} | |
with: | |
node-version: '16' | |
- name: Cache (NodeJS) | |
uses: actions/cache@v3 | |
if: ${{ env.TEST_LANGUAGE == 'nodejs' }} | |
with: | |
path: | | |
~/go/pkg/mod | |
~/.npm | |
key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }}-go-${{ hashFiles('**/go.sum') }} | |
restore-keys: | | |
${{ runner.os }}-node- | |
- uses: actions/setup-python@v5 | |
if: ${{ env.TEST_LANGUAGE == 'python' }} | |
with: | |
python-version: '3.x' | |
- name: Cache (Python) | |
uses: actions/cache@v3 | |
if: ${{ env.TEST_LANGUAGE == 'python' }} | |
with: | |
path: | | |
~/go/pkg/mod | |
~/.cache/pip | |
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}-go-${{ hashFiles('**/go.sum') }} | |
restore-keys: | | |
${{ runner.os }}-pip- | |
# FIXME: (enowell) Same as above - .NET uses the collector layer, but we | |
# only test Go with the collector layer so comment out for now. | |
# - name: Use .NET Language | |
# uses: actions/setup-dotnet@v1 | |
# if: ${{ env.TEST_LANGUAGE == 'dotnet' }} | |
# with: | |
# dotnet-version: '3.1.x' | |
- name: Use Go Language | |
uses: actions/setup-go@v5 | |
# NOTE: (enowell) In case the languages below need to build the | |
# collector, and because building the collector requires go 1.18 and | |
# above, always setup go 1.18. | |
# if: ${{ env.TEST_LANGUAGE == 'go' }} | |
with: | |
go-version: '~1.21.10' | |
check-latest: true | |
- name: download layer tf file | |
uses: actions/download-artifact@v4 | |
with: | |
name: layer_${{ matrix.architecture }}.tf | |
- name: Get terraform directory | |
run: | | |
echo TERRAFORM_DIRECTORY=${{ env.TEST_LANGUAGE }}/sample-apps/aws-sdk/deploy/${{ env.TEST_INSTR_TYPE }} | | |
tee --append $GITHUB_ENV | |
- name: overwrite layer.tf | |
run: | | |
OLD_LAYER_TF_FILE=layer_${{ matrix.architecture }}.tf | |
cat ${{ env.TERRAFORM_DIRECTORY }}/$OLD_LAYER_TF_FILE | |
mv -f layer.tf ${{ env.TERRAFORM_DIRECTORY }}/$OLD_LAYER_TF_FILE | |
cat ${{ env.TERRAFORM_DIRECTORY }}/$OLD_LAYER_TF_FILE | |
# NOTE: (enowell) This builds BOTH the sample app functions AND the | |
# layer. But we only use the sample app build in the release workflow. | |
# Also, we NEED the architecture value because some apps (like .NET and | |
# Go) depend on this architecture value. | |
- name: Build functions | |
run: GOARCH=${{ matrix.architecture }} ./build.sh ${{ matrix.architecture }} | |
working-directory: ${{ env.TEST_LANGUAGE }} | |
- uses: aws-actions/[email protected] | |
with: | |
role-to-assume: ${{ secrets.INTEG_TEST_LAMBDA_ROLE_ARN }} | |
role-duration-seconds: 1200 | |
aws-region: ${{ matrix.aws_region }} | |
- uses: hashicorp/setup-terraform@v2 | |
- name: Initialize terraform | |
run: terraform init | |
working-directory: ${{ env.TERRAFORM_DIRECTORY }} | |
- name: Get Lambda Layer `amd64` architecture value | |
if: ${{ matrix.architecture == 'amd64' }} | |
run: echo LAMBDA_FUNCTION_ARCH=x86_64 | tee --append $GITHUB_ENV | |
- name: Get Lambda Layer `arm64` architecture value | |
if: ${{ matrix.architecture == 'arm64' }} | |
run: echo LAMBDA_FUNCTION_ARCH=arm64 | tee --append $GITHUB_ENV | |
- name: Apply terraform | |
run: terraform apply -auto-approve | |
working-directory: ${{ env.TERRAFORM_DIRECTORY }} | |
env: | |
TF_VAR_function_name: lambda-${{ env.TEST_LANGUAGE }}-aws-sdk-${{ env.TEST_INSTR_TYPE }}-${{ matrix.architecture }}-${{ github.run_id }}-${{ matrix.aws_region }} | |
TF_VAR_architecture: ${{ env.LAMBDA_FUNCTION_ARCH }} | |
- name: Extract endpoint | |
id: extract-endpoint | |
run: terraform output -raw api-gateway-url | |
working-directory: ${{ env.TERRAFORM_DIRECTORY }} | |
- name: Extract AMP endpoint | |
if: ${{ env.TEST_LANGUAGE == 'java' && env.TEST_INSTR_TYPE == 'agent' && contains(env.AMP_REGIONS, matrix.aws_region) }} | |
id: extract-amp-endpoint | |
run: terraform output -raw amp_endpoint | |
working-directory: ${{ env.TERRAFORM_DIRECTORY }} | |
- name: Send request to endpoint | |
run: curl -sS ${{ steps.extract-endpoint.outputs.stdout }} | |
- name: Checkout test framework | |
uses: actions/checkout@v4 | |
with: | |
repository: aws-observability/aws-otel-test-framework | |
path: test-framework | |
- name: validate trace sample | |
run: | | |
cp adot/utils/expected-templates/${{ env.TEST_LANGUAGE }}-aws-sdk-${{ env.TEST_INSTR_TYPE }}.json test-framework/validator/src/main/resources/expected-data-template/lambdaExpectedTrace.mustache | |
cd test-framework | |
./gradlew :validator:run --args="-c default-lambda-validation.yml --endpoint ${{ steps.extract-endpoint.outputs.stdout }} --region $AWS_REGION" | |
- name: validate java agent metric sample | |
if: ${{ env.TEST_LANGUAGE == 'java' && env.TEST_INSTR_TYPE == 'agent' && contains(env.AMP_REGIONS, matrix.aws_region) }} | |
run: | | |
cp adot/utils/expected-templates/${{ env.TEST_LANGUAGE }}-aws-sdk-${{ env.TEST_INSTR_TYPE }}-metric.json test-framework/validator/src/main/resources/expected-data-template/ampExpectedMetric.mustache | |
cd test-framework | |
./gradlew :validator:run --args="-c prometheus-static-metric-validation.yml --cortex-instance-endpoint ${{ steps.extract-amp-endpoint.outputs.stdout }} --region $AWS_REGION" | |
- name: Destroy terraform | |
if: always() | |
run: terraform destroy -auto-approve | |
working-directory: ${{ env.TERRAFORM_DIRECTORY }} | |
env: | |
TF_VAR_architecture: ${{ env.LAMBDA_FUNCTION_ARCH }} |