Skip to content

Commit 8ecf5dd

Browse files
authored
improv(ci): automated the Gamma/Prod deployment to China/GovCloud regions (#4864)
1 parent ddad329 commit 8ecf5dd

File tree

2 files changed

+156
-119
lines changed

2 files changed

+156
-119
lines changed
Lines changed: 35 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Partitioned Layer Publish
22
# ---
3-
# This workflow publishes a specific layer version in an AWS account based on the environment input.
3+
# This workflow publishes a specific layer version in an AWS account based on the partition input.
44
#
5-
# We pull each the version of the layer and store them as artifacts, the we upload them to each of the Partitioned AWS accounts.
5+
# We pull the version of the layer and store them as artifacts, then we upload them to each of the Partitioned AWS accounts.
66
#
77
# A number of safety checks are performed to ensure safety.
88
#
@@ -12,7 +12,7 @@
1212
# 3. [Copy & Verify] deploy the layer to all regions in the target partition and validate layer deployment by comparing SHA256, description, and version numbers
1313
#
1414
# === Manual activities ===
15-
# 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
15+
# 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
1616
# 2. Monitor deployment progress and verify successful layer publication across all target regions
1717
# 3. Once this workflow is completed, the PR for the documentation update can me merged
1818
#
@@ -27,36 +27,15 @@
2727
on:
2828
workflow_dispatch:
2929
inputs:
30-
environment:
31-
description: Deployment environment
32-
type: choice
33-
options:
34-
- Gamma
35-
- Prod
36-
required: true
37-
version:
38-
description: Layer version to duplicate
39-
type: string
40-
required: true
4130
partition:
4231
description: Partition to deploy to
4332
type: choice
4433
options:
4534
- China
4635
- GovCloud
47-
workflow_call:
48-
inputs:
49-
environment:
50-
description: Deployment environment
51-
type: string
52-
required: true
53-
version:
54-
description: Layer version to duplicate
55-
type: string
56-
required: true
5736

5837
name: Layer Deployment (Partitions)
59-
run-name: Layer Deployment (${{ inputs.partition }}) - ${{ inputs.environment }} / Version - ${{ inputs.version }}
38+
run-name: Layer Deployment (${{ inputs.partition }})
6039

6140
permissions:
6241
contents: read
@@ -98,9 +77,15 @@ jobs:
9877
role-to-assume: ${{ secrets.AWS_IAM_ROLE }}
9978
aws-region: us-east-1
10079
mask-aws-account-id: true
80+
- name: Get Latest Layer Version
81+
id: get_latest_layer_version
82+
run: |
83+
set -euo pipefail
84+
LAYER_VERSION=$(aws lambda list-layer-versions --layer-name AWSLambdaPowertoolsTypeScriptV2 --query 'LayerVersions[0].Version' --output text --region us-east-1)
85+
echo "LAYER_VERSION=$LAYER_VERSION" >> $GITHUB_OUTPUT
10186
- name: Grab Zip
10287
env:
103-
VERSION: ${{ inputs.version }}
88+
VERSION: ${{ steps.get_latest_layer_version.outputs.LAYER_VERSION }}
10489
run: |
10590
set -euo pipefail
10691
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
@@ -119,96 +104,27 @@ jobs:
119104
path: AWSLambdaPowertoolsTypeScriptV2.json
120105
retention-days: 1
121106
if-no-files-found: error
122-
# 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.
123-
copy:
124-
name: Copy
125-
needs:
126-
- setup
127-
- download
128-
runs-on: ubuntu-latest
129-
permissions:
130-
id-token: write
131-
contents: read
132-
# Environment should interperlate as "GovCloud Prod" or "China Beta"
133-
environment: ${{ inputs.partition }} ${{ inputs.environment }}
134-
strategy:
135-
matrix:
136-
region: ${{ fromJson(needs.setup.outputs.regions) }}
137-
steps:
138-
- name: Download Zip
139-
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
140-
with:
141-
name: AWSLambdaPowertoolsTypeScriptV2.zip
142-
- name: Download Metadata
143-
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
144-
with:
145-
name: AWSLambdaPowertoolsTypeScriptV2.json
146-
- name: Verify Layer Signature
147-
run: |
148-
SHA=$(jq -r '.Content.CodeSha256' 'AWSLambdaPowertoolsTypeScriptV2.json')
149-
test "$(openssl dgst -sha256 -binary AWSLambdaPowertoolsTypeScriptV2.zip | openssl enc -base64)" == "$SHA" && echo "SHA OK: ${SHA}" || exit 1
150-
- id: transform
151-
run: |
152-
echo 'CONVERTED_REGION=${{ matrix.region }}' | tr 'a-z\-' 'A-Z_' >> "$GITHUB_OUTPUT"
153-
- name: Configure AWS Credentials
154-
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
155-
with:
156-
# Dynamic secret access is safe here - secrets are scoped per environment
157-
role-to-assume: ${{ secrets[format('IAM_ROLE_{0}', steps.transform.outputs.CONVERTED_REGION)] }}
158-
aws-region: ${{ matrix.region}}
159-
mask-aws-account-id: true
160-
audience: ${{ needs.setup.outputs.aud }}
161-
- name: Create Layer
162-
id: create-layer
163-
run: |
164-
set -euo pipefail
165-
cat AWSLambdaPowertoolsTypeScriptV2.json | jq '{"LayerName": "AWSLambdaPowertoolsTypeScriptV2", "Description": .Description, "CompatibleRuntimes": .CompatibleRuntimes, "LicenseInfo": .LicenseInfo}' > input.json
166-
167-
LAYER_VERSION=$(aws --region "${{ matrix.region }}" lambda publish-layer-version \
168-
--zip-file fileb://./AWSLambdaPowertoolsTypeScriptV2.zip \
169-
--cli-input-json file://./input.json \
170-
--query 'Version' \
171-
--output text)
172-
173-
echo "LAYER_VERSION=$LAYER_VERSION" >> "$GITHUB_OUTPUT"
174-
175-
aws --region "${{ matrix.region }}" lambda add-layer-version-permission \
176-
--layer-name 'AWSLambdaPowertoolsTypeScriptV2' \
177-
--statement-id 'PublicLayer' \
178-
--action lambda:GetLayerVersion \
179-
--principal '*' \
180-
--version-number "$LAYER_VERSION"
181-
# This step retrieves the newly deployed layer metadata and compares it against the original source layer:
182-
# 1. SHA256 hash verification - ensures the layer content is identical to the source
183-
# 2. Description validation - confirms the version number in the description matches the source
184-
# 3. Layer Version number verification - validates that the layer version numbers match between source and target
185-
# 4. Tabular comparison output - displays side-by-side comparison of key layer properties
186-
- name: Verify Layer
187-
env:
188-
LAYER_VERSION: ${{ steps.create-layer.outputs.LAYER_VERSION }}
189-
ENVIRONMENT: ${{ inputs.environment }}
190-
run: |
191-
set -euo pipefail
192-
export layer_output="AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json"
193-
# Dynamic secret access is safe here - secrets are scoped per environment
194-
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"
195-
REMOTE_SHA=$(jq -r '.Content.CodeSha256' $layer_output)
196-
LOCAL_SHA=$(jq -r '.Content.CodeSha256' AWSLambdaPowertoolsTypeScriptV2.json)
197-
test "$REMOTE_SHA" == "$LOCAL_SHA" && echo "SHA OK: ${LOCAL_SHA}" || exit 1
198-
REMOTE_DESCRIPTION=$(jq -r '.Description' $layer_output)
199-
LOCAL_DESCRIPTION=$(jq -r '.Description' AWSLambdaPowertoolsTypeScriptV2.json)
200-
test "$REMOTE_DESCRIPTION" == "$LOCAL_DESCRIPTION" && echo "Version number OK: ${LOCAL_DESCRIPTION}" || exit 1
201-
if [ "$ENVIRONMENT" == "Prod" ]; then
202-
REMOTE_LAYER_VERSION=$(jq -r '.LayerVersionArn' $layer_output | sed 's/.*://')
203-
LOCAL_LAYER_VERSION=$(jq -r '.LayerVersionArn' AWSLambdaPowertoolsTypeScriptV2.json | sed 's/.*://')
204-
test "$REMOTE_LAYER_VERSION" == "$LOCAL_LAYER_VERSION" && echo "Layer Version number OK: ${LOCAL_LAYER_VERSION}" || exit 1
205-
fi
206-
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'
207-
208-
- name: Store Metadata - ${{ matrix.region }}
209-
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
210-
with:
211-
name: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json
212-
path: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json
213-
retention-days: 1
214-
if-no-files-found: error
107+
# Copies the Layer to the Gamma Environment in the selected partition
108+
deploy-gamma:
109+
name: Deploy Gamma Layer
110+
needs: [setup, download]
111+
uses: ./.github/workflows/layers_partitions_deploy.yml
112+
with:
113+
environment: Gamma
114+
partition: ${{ inputs.partition }}
115+
arn_partition: ${{ needs.setup.outputs.partition }}
116+
regions: ${{ needs.setup.outputs.regions }}
117+
aud: ${{ needs.setup.outputs.aud }}
118+
secrets: inherit
119+
# Copies the Layer to the Prod Environment in the selected partition
120+
deploy-prod:
121+
name: Deploy Prod Layer
122+
needs: [setup, download, deploy-gamma]
123+
uses: ./.github/workflows/layers_partitions_deploy.yml
124+
with:
125+
environment: Prod
126+
partition: ${{ inputs.partition }}
127+
arn_partition: ${{ needs.setup.outputs.partition }}
128+
regions: ${{ needs.setup.outputs.regions }}
129+
aud: ${{ needs.setup.outputs.aud }}
130+
secrets: inherit
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
name: Copy Layer
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
environment:
7+
required: true
8+
type: string
9+
partition:
10+
required: true
11+
type: string
12+
arn_partition:
13+
required: true
14+
type: string
15+
regions:
16+
required: true
17+
type: string
18+
aud:
19+
required: true
20+
type: string
21+
22+
jobs:
23+
# 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.
24+
deploy:
25+
name: Deploy ${{ inputs.partition }} - ${{ inputs.environment }}
26+
runs-on: ubuntu-latest
27+
permissions:
28+
id-token: write
29+
contents: read
30+
environment: ${{ inputs.partition }} ${{ inputs.environment }}
31+
strategy:
32+
matrix:
33+
region: ${{ fromJson(inputs.regions) }}
34+
steps:
35+
- name: Download Zip
36+
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
37+
with:
38+
name: AWSLambdaPowertoolsTypeScriptV2.zip
39+
40+
- name: Download Metadata
41+
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
42+
with:
43+
name: AWSLambdaPowertoolsTypeScriptV2.json
44+
45+
- name: Verify Layer Signature
46+
run: |
47+
SHA=$(jq -r '.Content.CodeSha256' 'AWSLambdaPowertoolsTypeScriptV2.json')
48+
test "$(openssl dgst -sha256 -binary AWSLambdaPowertoolsTypeScriptV2.zip | openssl enc -base64)" == "$SHA" && echo "SHA OK: ${SHA}" || exit 1
49+
50+
- id: transform
51+
run: |
52+
echo 'CONVERTED_REGION=${{ matrix.region }}' | tr 'a-z\-' 'A-Z_' >> "$GITHUB_OUTPUT"
53+
54+
- name: Configure AWS Credentials
55+
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
56+
with:
57+
role-to-assume: ${{ secrets[format('IAM_ROLE_{0}', steps.transform.outputs.CONVERTED_REGION)] }}
58+
aws-region: ${{ matrix.region }}
59+
mask-aws-account-id: true
60+
audience: ${{ inputs.aud }}
61+
62+
- name: Create Layer
63+
id: create-layer
64+
run: |
65+
set -euo pipefail
66+
cat AWSLambdaPowertoolsTypeScriptV2.json | jq '{"LayerName": "AWSLambdaPowertoolsTypeScriptV2", "Description": .Description, "CompatibleRuntimes": .CompatibleRuntimes, "LicenseInfo": .LicenseInfo}' > input.json
67+
68+
LAYER_VERSION=$(aws --region "${{ matrix.region }}" lambda publish-layer-version \
69+
--zip-file fileb://./AWSLambdaPowertoolsTypeScriptV2.zip \
70+
--cli-input-json file://./input.json \
71+
--query 'Version' \
72+
--output text)
73+
74+
echo "LAYER_VERSION=$LAYER_VERSION" >> "$GITHUB_OUTPUT"
75+
76+
aws --region "${{ matrix.region }}" lambda add-layer-version-permission \
77+
--layer-name 'AWSLambdaPowertoolsTypeScriptV2' \
78+
--statement-id 'PublicLayer' \
79+
--action lambda:GetLayerVersion \
80+
--principal '*' \
81+
--version-number "$LAYER_VERSION"
82+
83+
# This step retrieves the newly deployed layer metadata and compares it against the original source layer:
84+
# 1. SHA256 hash verification - ensures the layer content is identical to the source
85+
# 2. Description validation - confirms the version number in the description matches the source
86+
# 3. Layer Version number verification - validates that the layer version numbers match between source and target
87+
# 4. Tabular comparison output - displays side-by-side comparison of key layer properties
88+
- name: Verify Layer
89+
env:
90+
LAYER_VERSION: ${{ steps.create-layer.outputs.LAYER_VERSION }}
91+
ENVIRONMENT: ${{ inputs.environment }}
92+
PARTITION: ${{ inputs.arn_partition }}
93+
run: |
94+
set -euo pipefail
95+
export layer_output="AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json"
96+
# Dynamic secret access is safe here - secrets are scoped per environment
97+
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"
98+
99+
REMOTE_SHA=$(jq -r '.Content.CodeSha256' "$layer_output")
100+
LOCAL_SHA=$(jq -r '.Content.CodeSha256' AWSLambdaPowertoolsTypeScriptV2.json)
101+
test "$REMOTE_SHA" == "$LOCAL_SHA" && echo "SHA OK: ${LOCAL_SHA}" || exit 1
102+
103+
REMOTE_DESCRIPTION=$(jq -r '.Description' "$layer_output")
104+
LOCAL_DESCRIPTION=$(jq -r '.Description' AWSLambdaPowertoolsTypeScriptV2.json)
105+
test "$REMOTE_DESCRIPTION" == "$LOCAL_DESCRIPTION" && echo "Description OK: ${LOCAL_DESCRIPTION}" || exit 1
106+
107+
if [ "$ENVIRONMENT" == "Prod" ]; then
108+
REMOTE_LAYER_VERSION=$(jq -r '.LayerVersionArn' "$layer_output" | sed 's/.*://')
109+
LOCAL_LAYER_VERSION=$(jq -r '.LayerVersionArn' AWSLambdaPowertoolsTypeScriptV2.json | sed 's/.*://')
110+
test "$REMOTE_LAYER_VERSION" == "$LOCAL_LAYER_VERSION" && echo "Layer Version number OK: ${LOCAL_LAYER_VERSION}" || exit 1
111+
fi
112+
113+
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'
114+
115+
- name: Store Metadata - ${{ matrix.region }}
116+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
117+
with:
118+
name: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json
119+
path: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json
120+
retention-days: 1
121+
if-no-files-found: error

0 commit comments

Comments
 (0)