Skip to content
Merged
Show file tree
Hide file tree
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
154 changes: 35 additions & 119 deletions .github/workflows/layers_partitions.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Partitioned Layer Publish
# ---
# This workflow publishes a specific layer version in an AWS account based on the environment input.
# This workflow publishes a specific layer version in an AWS account based on the partition input.
#
# We pull each the version of the layer and store them as artifacts, the we upload them to each of the Partitioned AWS accounts.
# We pull the version of the layer and store them as artifacts, then we upload them to each of the Partitioned AWS accounts.
#
# A number of safety checks are performed to ensure safety.
#
Expand All @@ -12,7 +12,7 @@
# 3. [Copy & Verify] deploy the layer to all regions in the target partition and validate layer deployment by comparing SHA256, description, and version numbers
#
# === Manual activities ===
# 1. After the `make-release` workflow finishes and the PR for the documentation update gets created, trigger this workflow manually via `workflow_dispatch` with environment, version, and partition inputs for each Gamma and Prod environment in the China and GovCloud partitions
# 1. After the `make-release` workflow finishes and the PR for the documentation update gets created, trigger this workflow manually via `workflow_dispatch` with partition inputs for each China and GovCloud partitions
# 2. Monitor deployment progress and verify successful layer publication across all target regions
# 3. Once this workflow is completed, the PR for the documentation update can me merged
#
Expand All @@ -27,36 +27,15 @@
on:
workflow_dispatch:
inputs:
environment:
description: Deployment environment
type: choice
options:
- Gamma
- Prod
required: true
version:
description: Layer version to duplicate
type: string
required: true
partition:
description: Partition to deploy to
type: choice
options:
- China
- GovCloud
workflow_call:
inputs:
environment:
description: Deployment environment
type: string
required: true
version:
description: Layer version to duplicate
type: string
required: true

name: Layer Deployment (Partitions)
run-name: Layer Deployment (${{ inputs.partition }}) - ${{ inputs.environment }} / Version - ${{ inputs.version }}
run-name: Layer Deployment (${{ inputs.partition }})

permissions:
contents: read
Expand Down Expand Up @@ -98,9 +77,15 @@ jobs:
role-to-assume: ${{ secrets.AWS_IAM_ROLE }}
aws-region: us-east-1
mask-aws-account-id: true
- name: Get Latest Layer Version
id: get_latest_layer_version
run: |
set -euo pipefail
LAYER_VERSION=$(aws lambda list-layer-versions --layer-name AWSLambdaPowertoolsTypeScriptV2 --query 'LayerVersions[0].Version' --output text --region us-east-1)
echo "LAYER_VERSION=$LAYER_VERSION" >> $GITHUB_OUTPUT
- name: Grab Zip
env:
VERSION: ${{ inputs.version }}
VERSION: ${{ steps.get_latest_layer_version.outputs.LAYER_VERSION }}
run: |
set -euo pipefail
aws --region us-east-1 lambda get-layer-version-by-arn --arn "arn:aws:lambda:us-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:${VERSION}" --query 'Content.Location' | xargs curl -L -o AWSLambdaPowertoolsTypeScriptV2.zip
Expand All @@ -119,96 +104,27 @@ jobs:
path: AWSLambdaPowertoolsTypeScriptV2.json
retention-days: 1
if-no-files-found: error
# This job deploys the layer to all regions in the target partition using a matrix strategy. It performs integrity checks, publishes the layer, sets public permissions, and validates deployment.
copy:
name: Copy
needs:
- setup
- download
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
# Environment should interperlate as "GovCloud Prod" or "China Beta"
environment: ${{ inputs.partition }} ${{ inputs.environment }}
strategy:
matrix:
region: ${{ fromJson(needs.setup.outputs.regions) }}
steps:
- name: Download Zip
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: AWSLambdaPowertoolsTypeScriptV2.zip
- name: Download Metadata
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: AWSLambdaPowertoolsTypeScriptV2.json
- name: Verify Layer Signature
run: |
SHA=$(jq -r '.Content.CodeSha256' 'AWSLambdaPowertoolsTypeScriptV2.json')
test "$(openssl dgst -sha256 -binary AWSLambdaPowertoolsTypeScriptV2.zip | openssl enc -base64)" == "$SHA" && echo "SHA OK: ${SHA}" || exit 1
- id: transform
run: |
echo 'CONVERTED_REGION=${{ matrix.region }}' | tr 'a-z\-' 'A-Z_' >> "$GITHUB_OUTPUT"
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
with:
# Dynamic secret access is safe here - secrets are scoped per environment
role-to-assume: ${{ secrets[format('IAM_ROLE_{0}', steps.transform.outputs.CONVERTED_REGION)] }}
aws-region: ${{ matrix.region}}
mask-aws-account-id: true
audience: ${{ needs.setup.outputs.aud }}
- name: Create Layer
id: create-layer
run: |
set -euo pipefail
cat AWSLambdaPowertoolsTypeScriptV2.json | jq '{"LayerName": "AWSLambdaPowertoolsTypeScriptV2", "Description": .Description, "CompatibleRuntimes": .CompatibleRuntimes, "LicenseInfo": .LicenseInfo}' > input.json

LAYER_VERSION=$(aws --region "${{ matrix.region }}" lambda publish-layer-version \
--zip-file fileb://./AWSLambdaPowertoolsTypeScriptV2.zip \
--cli-input-json file://./input.json \
--query 'Version' \
--output text)

echo "LAYER_VERSION=$LAYER_VERSION" >> "$GITHUB_OUTPUT"

aws --region "${{ matrix.region }}" lambda add-layer-version-permission \
--layer-name 'AWSLambdaPowertoolsTypeScriptV2' \
--statement-id 'PublicLayer' \
--action lambda:GetLayerVersion \
--principal '*' \
--version-number "$LAYER_VERSION"
# This step retrieves the newly deployed layer metadata and compares it against the original source layer:
# 1. SHA256 hash verification - ensures the layer content is identical to the source
# 2. Description validation - confirms the version number in the description matches the source
# 3. Layer Version number verification - validates that the layer version numbers match between source and target
# 4. Tabular comparison output - displays side-by-side comparison of key layer properties
- name: Verify Layer
env:
LAYER_VERSION: ${{ steps.create-layer.outputs.LAYER_VERSION }}
ENVIRONMENT: ${{ inputs.environment }}
run: |
set -euo pipefail
export layer_output="AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json"
# Dynamic secret access is safe here - secrets are scoped per environment
aws --region "${{ matrix.region }}" lambda get-layer-version-by-arn --arn "arn:${{ needs.setup.outputs.partition }}:lambda:${{ matrix.region }}:${{ secrets[format('AWS_ACCOUNT_{0}', steps.transform.outputs.CONVERTED_REGION)] }}:layer:AWSLambdaPowertoolsTypeScriptV2:${LAYER_VERSION}" > "$layer_output"
REMOTE_SHA=$(jq -r '.Content.CodeSha256' $layer_output)
LOCAL_SHA=$(jq -r '.Content.CodeSha256' AWSLambdaPowertoolsTypeScriptV2.json)
test "$REMOTE_SHA" == "$LOCAL_SHA" && echo "SHA OK: ${LOCAL_SHA}" || exit 1
REMOTE_DESCRIPTION=$(jq -r '.Description' $layer_output)
LOCAL_DESCRIPTION=$(jq -r '.Description' AWSLambdaPowertoolsTypeScriptV2.json)
test "$REMOTE_DESCRIPTION" == "$LOCAL_DESCRIPTION" && echo "Version number OK: ${LOCAL_DESCRIPTION}" || exit 1
if [ "$ENVIRONMENT" == "Prod" ]; then
REMOTE_LAYER_VERSION=$(jq -r '.LayerVersionArn' $layer_output | sed 's/.*://')
LOCAL_LAYER_VERSION=$(jq -r '.LayerVersionArn' AWSLambdaPowertoolsTypeScriptV2.json | sed 's/.*://')
test "$REMOTE_LAYER_VERSION" == "$LOCAL_LAYER_VERSION" && echo "Layer Version number OK: ${LOCAL_LAYER_VERSION}" || exit 1
fi
jq -s -r '["Layer Arn", "Runtimes", "Version", "Description", "SHA256"], ([.[0], .[1]] | .[] | [.LayerArn, (.CompatibleRuntimes | join("/")), .Version, .Description, .Content.CodeSha256]) |@tsv' AWSLambdaPowertoolsTypeScriptV2.json $layer_output | column -t -s $'\t'

- name: Store Metadata - ${{ matrix.region }}
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json
path: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json
retention-days: 1
if-no-files-found: error
# Copies the Layer to the Gamma Environment in the selected partition
deploy-gamma:
name: Deploy Gamma Layer
needs: [setup, download]
uses: ./.github/workflows/layers_partitions_deploy.yml
with:
environment: Gamma
partition: ${{ inputs.partition }}
arn_partition: ${{ needs.setup.outputs.partition }}
regions: ${{ needs.setup.outputs.regions }}
aud: ${{ needs.setup.outputs.aud }}
secrets: inherit
# Copies the Layer to the Prod Environment in the selected partition
deploy-prod:
name: Deploy Prod Layer
needs: [setup, download, deploy-gamma]
uses: ./.github/workflows/layers_partitions_deploy.yml
with:
environment: Prod
partition: ${{ inputs.partition }}
arn_partition: ${{ needs.setup.outputs.partition }}
regions: ${{ needs.setup.outputs.regions }}
aud: ${{ needs.setup.outputs.aud }}
secrets: inherit
121 changes: 121 additions & 0 deletions .github/workflows/layers_partitions_deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
name: Copy Layer

on:
workflow_call:
inputs:
environment:
required: true
type: string
partition:
required: true
type: string
arn_partition:
required: true
type: string
regions:
required: true
type: string
aud:
required: true
type: string

jobs:
# This job deploys the layer to all regions in the target partition using a matrix strategy. It performs integrity checks, publishes the layer, sets public permissions, and validates deployment.
deploy:
name: Deploy ${{ inputs.partition }} - ${{ inputs.environment }}
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
environment: ${{ inputs.partition }} ${{ inputs.environment }}
strategy:
matrix:
region: ${{ fromJson(inputs.regions) }}
steps:
- name: Download Zip
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: AWSLambdaPowertoolsTypeScriptV2.zip

- name: Download Metadata
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: AWSLambdaPowertoolsTypeScriptV2.json

- name: Verify Layer Signature
run: |
SHA=$(jq -r '.Content.CodeSha256' 'AWSLambdaPowertoolsTypeScriptV2.json')
test "$(openssl dgst -sha256 -binary AWSLambdaPowertoolsTypeScriptV2.zip | openssl enc -base64)" == "$SHA" && echo "SHA OK: ${SHA}" || exit 1

- id: transform
run: |
echo 'CONVERTED_REGION=${{ matrix.region }}' | tr 'a-z\-' 'A-Z_' >> "$GITHUB_OUTPUT"

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
with:
role-to-assume: ${{ secrets[format('IAM_ROLE_{0}', steps.transform.outputs.CONVERTED_REGION)] }}
aws-region: ${{ matrix.region }}
mask-aws-account-id: true
audience: ${{ inputs.aud }}

- name: Create Layer
id: create-layer
run: |
set -euo pipefail
cat AWSLambdaPowertoolsTypeScriptV2.json | jq '{"LayerName": "AWSLambdaPowertoolsTypeScriptV2", "Description": .Description, "CompatibleRuntimes": .CompatibleRuntimes, "LicenseInfo": .LicenseInfo}' > input.json

LAYER_VERSION=$(aws --region "${{ matrix.region }}" lambda publish-layer-version \
--zip-file fileb://./AWSLambdaPowertoolsTypeScriptV2.zip \
--cli-input-json file://./input.json \
--query 'Version' \
--output text)

echo "LAYER_VERSION=$LAYER_VERSION" >> "$GITHUB_OUTPUT"

aws --region "${{ matrix.region }}" lambda add-layer-version-permission \
--layer-name 'AWSLambdaPowertoolsTypeScriptV2' \
--statement-id 'PublicLayer' \
--action lambda:GetLayerVersion \
--principal '*' \
--version-number "$LAYER_VERSION"

# This step retrieves the newly deployed layer metadata and compares it against the original source layer:
# 1. SHA256 hash verification - ensures the layer content is identical to the source
# 2. Description validation - confirms the version number in the description matches the source
# 3. Layer Version number verification - validates that the layer version numbers match between source and target
# 4. Tabular comparison output - displays side-by-side comparison of key layer properties
- name: Verify Layer
env:
LAYER_VERSION: ${{ steps.create-layer.outputs.LAYER_VERSION }}
ENVIRONMENT: ${{ inputs.environment }}
PARTITION: ${{ inputs.arn_partition }}
run: |
set -euo pipefail
export layer_output="AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json"
# Dynamic secret access is safe here - secrets are scoped per environment
aws --region "${{ matrix.region }}" lambda get-layer-version-by-arn --arn "arn:${PARTITION}:lambda:${{ matrix.region }}:${{ secrets[format('AWS_ACCOUNT_{0}', steps.transform.outputs.CONVERTED_REGION)] }}:layer:AWSLambdaPowertoolsTypeScriptV2:${LAYER_VERSION}" > "$layer_output"

REMOTE_SHA=$(jq -r '.Content.CodeSha256' "$layer_output")
LOCAL_SHA=$(jq -r '.Content.CodeSha256' AWSLambdaPowertoolsTypeScriptV2.json)
test "$REMOTE_SHA" == "$LOCAL_SHA" && echo "SHA OK: ${LOCAL_SHA}" || exit 1

REMOTE_DESCRIPTION=$(jq -r '.Description' "$layer_output")
LOCAL_DESCRIPTION=$(jq -r '.Description' AWSLambdaPowertoolsTypeScriptV2.json)
test "$REMOTE_DESCRIPTION" == "$LOCAL_DESCRIPTION" && echo "Description OK: ${LOCAL_DESCRIPTION}" || exit 1

if [ "$ENVIRONMENT" == "Prod" ]; then
REMOTE_LAYER_VERSION=$(jq -r '.LayerVersionArn' "$layer_output" | sed 's/.*://')
LOCAL_LAYER_VERSION=$(jq -r '.LayerVersionArn' AWSLambdaPowertoolsTypeScriptV2.json | sed 's/.*://')
test "$REMOTE_LAYER_VERSION" == "$LOCAL_LAYER_VERSION" && echo "Layer Version number OK: ${LOCAL_LAYER_VERSION}" || exit 1
fi

jq -s -r '["Layer Arn", "Runtimes", "Version", "Description", "SHA256"], ([.[0], .[1]] | .[] | [.LayerArn, (.CompatibleRuntimes | join("/")), .Version, .Description, .Content.CodeSha256]) |@tsv' AWSLambdaPowertoolsTypeScriptV2.json "$layer_output" | column -t -s $'\t'

- name: Store Metadata - ${{ matrix.region }}
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json
path: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json
retention-days: 1
if-no-files-found: error