Skip to content

Commit 17a2839

Browse files
committed
ci: add workflow for lambda layer publish and yank
1 parent 45dd597 commit 17a2839

File tree

3 files changed

+326
-0
lines changed

3 files changed

+326
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Lambda Layers Standard Operating Procedures (SOP)
2+
3+
## Overview
4+
5+
This document defines the standard operating procedures for managing Strands Agents Lambda layers across all AWS regions, Python versions, and architectures.
6+
7+
**Total: 136 individual Lambda layers** (17 regions × 2 architectures × 4 Python versions). All variants must maintain the same layer version number for each PyPI package version, with only one row per PyPI version appearing in documentation.
8+
9+
## Deployment Process
10+
11+
### 1. Initial Deployment
12+
1. Run workflow with ALL options selected (default)
13+
2. Specify PyPI package version
14+
3. Type "Create Lambda Layer {package_version}" to confirm
15+
4. All 136 individual layers deploy in parallel (4 Python × 2 arch × 17 regions)
16+
5. Each layer gets its own unique name: `strands-agents-py{PYTHON_VERSION}-{ARCH}`
17+
18+
### 2. Version Buffering for New Variants
19+
When adding new variants (new Python version, architecture, or region):
20+
21+
1. **Determine target layer version**: Check existing variants to find the highest layer version
22+
2. **Buffer deployment**: Deploy new variants multiple times until layer version matches existing variants
23+
3. **Example**: If existing variants are at layer version 5, deploy new variant 5 times to reach version 5
24+
25+
### 3. Handling Transient Failures
26+
When some regions fail during deployment:
27+
28+
1. **Identify failed regions**: Check which combinations didn't complete successfully
29+
2. **Targeted redeployment**: Use specific region/arch/Python inputs to redeploy failed combinations
30+
3. **Version alignment**: Continue deploying until all variants reach the same layer version
31+
4. **Verification**: Confirm all combinations have identical layer versions before updating docs
32+
33+
## Yank Process
34+
35+
### Yank Procedure
36+
1. Use the `yank_lambda_layer` GitHub action workflow
37+
2. Specify the layer version to yank
38+
3. Type "Yank Lambda Layer {layer_version}" to confirm
39+
4. **Full yank**: Run with ALL options selected (default) to yank all 136 variants OR **Partial yank**: Specify Python versions, architectures, and regions for targeted yanking
40+
6. Update documentation
41+
7. **Communication**: Notify users through appropriate channels
42+
43+
**Note**: Yanking deletes layer versions completely. Existing Lambda functions using the layer continue to work, but new functions cannot use the yanked version.
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
name: Publish PyPI Package to Lambda Layer
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
package_version:
7+
description: 'Package version to download'
8+
required: true
9+
type: string
10+
layer_version:
11+
description: 'Layer version'
12+
required: true
13+
type: string
14+
python_version:
15+
description: 'Python version'
16+
required: true
17+
default: 'ALL'
18+
type: choice
19+
options: ['ALL', '3.10', '3.11', '3.12', '3.13']
20+
architecture:
21+
description: 'Architecture'
22+
required: true
23+
default: 'ALL'
24+
type: choice
25+
options: ['ALL', 'x86_64', 'aarch64']
26+
region:
27+
description: 'AWS region'
28+
required: true
29+
default: 'ALL'
30+
type: choice
31+
# Only non opt-in regions included for now
32+
options: ['ALL', 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'ap-south-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3', 'ap-southeast-1', 'ap-southeast-2', 'ca-central-1', 'eu-central-1', 'eu-west-1', 'eu-west-2', 'eu-west-3', 'eu-north-1', 'sa-east-1']
33+
confirm:
34+
description: 'Type "Create Lambda Layer {PyPI version}-layer{layer version}" to confirm publishing the layer'
35+
required: true
36+
type: string
37+
38+
env:
39+
BUCKET_NAME: strands-agents-lambda-layer
40+
41+
jobs:
42+
validate:
43+
runs-on: ubuntu-latest
44+
steps:
45+
- name: Validate confirmation
46+
run: |
47+
CONFIRM="${{ inputs.confirm }}"
48+
EXPECTED="Create Lambda Layer ${{ inputs.package_version }}-layer${{ inputs.layer_version }}"
49+
if [ "$CONFIRM" != "$EXPECTED" ]; then
50+
echo "Confirmation failed. You must type exactly '$EXPECTED' to proceed."
51+
exit 1
52+
fi
53+
echo "Confirmation validated"
54+
55+
create-buckets:
56+
needs: validate
57+
runs-on: ubuntu-latest
58+
strategy:
59+
matrix:
60+
region: ${{ inputs.region == 'ALL' && fromJson('["us-east-1", "us-east-2", "us-west-1", "us-west-2", "ap-south-1", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "sa-east-1"]') || fromJson(format('["{0}"]', inputs.region)) }}
61+
permissions:
62+
id-token: write
63+
steps:
64+
- name: Configure AWS credentials
65+
uses: aws-actions/configure-aws-credentials@v4
66+
with:
67+
role-to-assume: ${{ secrets.STRANDS_LAMBDA_LAYER_PUBLISHER_ROLE }}
68+
aws-region: ${{ matrix.region }}
69+
70+
- name: Create S3 bucket
71+
run: |
72+
REGION="${{ matrix.region }}"
73+
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
74+
REGIONAL_BUCKET="${{ env.BUCKET_NAME }}-${ACCOUNT_ID}-${REGION}"
75+
76+
if ! aws s3api head-bucket --bucket "$REGIONAL_BUCKET" 2>/dev/null; then
77+
if [ "$REGION" = "us-east-1" ]; then
78+
aws s3api create-bucket --bucket "$REGIONAL_BUCKET" --region "$REGION" 2>/dev/null || echo "Bucket $REGIONAL_BUCKET already exists"
79+
else
80+
aws s3api create-bucket --bucket "$REGIONAL_BUCKET" --region "$REGION" --create-bucket-configuration LocationConstraint="$REGION" 2>/dev/null || echo "Bucket $REGIONAL_BUCKET already exists"
81+
fi
82+
echo "S3 bucket ready: $REGIONAL_BUCKET"
83+
else
84+
echo "S3 bucket already exists: $REGIONAL_BUCKET"
85+
fi
86+
87+
package-and-upload:
88+
needs: create-buckets
89+
runs-on: ubuntu-latest
90+
strategy:
91+
matrix:
92+
python-version: ${{ inputs.python_version == 'ALL' && fromJson('["3.10", "3.11", "3.12", "3.13"]') || fromJson(format('["{0}"]', inputs.python_version)) }}
93+
architecture: ${{ inputs.architecture == 'ALL' && fromJson('["x86_64", "aarch64"]') || fromJson(format('["{0}"]', inputs.architecture)) }}
94+
region: ${{ inputs.region == 'ALL' && fromJson('["us-east-1", "us-east-2", "us-west-1", "us-west-2", "ap-south-1", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "sa-east-1"]') || fromJson(format('["{0}"]', inputs.region)) }}
95+
96+
permissions:
97+
id-token: write
98+
99+
steps:
100+
- name: Set up Python
101+
uses: actions/setup-python@v4
102+
with:
103+
python-version: ${{ matrix.python-version }}
104+
105+
- name: Configure AWS credentials
106+
uses: aws-actions/configure-aws-credentials@v4
107+
with:
108+
role-to-assume: ${{ secrets.STRANDS_LAMBDA_LAYER_PUBLISHER_ROLE }}
109+
aws-region: ${{ matrix.region }}
110+
111+
- name: Create layer directory structure
112+
run: |
113+
mkdir -p layer/python
114+
115+
- name: Download and install package
116+
run: |
117+
pip install strands-agents==${{ inputs.package_version }} \
118+
--python-version ${{ matrix.python-version }} \
119+
--platform manylinux2014_${{ matrix.architecture }} \
120+
-t layer/python/ \
121+
--only-binary=:all:
122+
123+
- name: Create layer zip
124+
run: |
125+
cd layer
126+
zip -r ../lambda-layer.zip .
127+
128+
- name: Upload to S3
129+
run: |
130+
PYTHON_VERSION="${{ matrix.python-version }}"
131+
ARCH="${{ matrix.architecture }}"
132+
REGION="${{ matrix.region }}"
133+
LAYER_NAME="strands-agents-py${PYTHON_VERSION//./_}-${ARCH}"
134+
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
135+
BUCKET_NAME="${{ env.BUCKET_NAME }}-${ACCOUNT_ID}-${REGION}"
136+
LAYER_KEY="$LAYER_NAME/${{ inputs.package_version }}/layer${{ inputs.layer_version }}/lambda-layer.zip"
137+
138+
aws s3 cp lambda-layer.zip "s3://$BUCKET_NAME/$LAYER_KEY" --region "$REGION"
139+
echo "Uploaded layer to s3://$BUCKET_NAME/$LAYER_KEY"
140+
141+
publish-layer:
142+
needs: package-and-upload
143+
runs-on: ubuntu-latest
144+
strategy:
145+
matrix:
146+
python-version: ${{ inputs.python_version == 'ALL' && fromJson('["3.10", "3.11", "3.12", "3.13"]') || fromJson(format('["{0}"]', inputs.python_version)) }}
147+
architecture: ${{ inputs.architecture == 'ALL' && fromJson('["x86_64", "aarch64"]') || fromJson(format('["{0}"]', inputs.architecture)) }}
148+
region: ${{ inputs.region == 'ALL' && fromJson('["us-east-1", "us-east-2", "us-west-1", "us-west-2", "ap-south-1", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "sa-east-1"]') || fromJson(format('["{0}"]', inputs.region)) }}
149+
150+
permissions:
151+
id-token: write
152+
153+
steps:
154+
- name: Configure AWS credentials
155+
uses: aws-actions/configure-aws-credentials@v4
156+
with:
157+
role-to-assume: ${{ secrets.STRANDS_LAMBDA_LAYER_PUBLISHER_ROLE }}
158+
aws-region: ${{ matrix.region }}
159+
160+
- name: Publish layer
161+
run: |
162+
PYTHON_VERSION="${{ matrix.python-version }}"
163+
ARCH="${{ matrix.architecture }}"
164+
REGION="${{ matrix.region }}"
165+
LAYER_NAME="strands-agents-py${PYTHON_VERSION//./_}-${ARCH}"
166+
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
167+
REGION_BUCKET="${{ env.BUCKET_NAME }}-${ACCOUNT_ID}-${REGION}"
168+
LAYER_KEY="$LAYER_NAME/${{ inputs.package_version }}/layer${{ inputs.layer_version }}/lambda-layer.zip"
169+
170+
DESCRIPTION="PyPI package: strands-agents v${{ inputs.package_version }} (Python $PYTHON_VERSION, $ARCH)"
171+
172+
# Set compatible architecture based on matrix architecture
173+
if [ "$ARCH" = "x86_64" ]; then
174+
COMPATIBLE_ARCH="x86_64"
175+
else
176+
COMPATIBLE_ARCH="arm64"
177+
fi
178+
179+
LAYER_OUTPUT=$(aws lambda publish-layer-version \
180+
--layer-name $LAYER_NAME \
181+
--description "$DESCRIPTION" \
182+
--content S3Bucket=$REGION_BUCKET,S3Key=$LAYER_KEY \
183+
--compatible-runtimes python${{ matrix.python-version }} \
184+
--compatible-architectures $COMPATIBLE_ARCH \
185+
--region "$REGION" \
186+
--license-info Apache-2.0 \
187+
--output json)
188+
189+
LAYER_ARN=$(echo "$LAYER_OUTPUT" | jq -r '.LayerArn')
190+
LAYER_VERSION=$(echo "$LAYER_OUTPUT" | jq -r '.Version')
191+
192+
echo "Published layer version $LAYER_VERSION with ARN: $LAYER_ARN in region $REGION"
193+
194+
aws lambda add-layer-version-permission \
195+
--layer-name $LAYER_NAME \
196+
--version-number $LAYER_VERSION \
197+
--statement-id public \
198+
--action lambda:GetLayerVersion \
199+
--principal '*' \
200+
--region "$REGION"
201+
202+
echo "Successfully published layer version $LAYER_VERSION in region $REGION"
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: Yank Lambda Layer
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
layer_version:
7+
description: 'Layer version to yank'
8+
required: true
9+
type: string
10+
python_version:
11+
description: 'Python version'
12+
required: true
13+
default: 'ALL'
14+
type: choice
15+
options: ['ALL', '3.10', '3.11', '3.12', '3.13']
16+
architecture:
17+
description: 'Architecture'
18+
required: true
19+
default: 'ALL'
20+
type: choice
21+
options: ['ALL', 'x86_64', 'aarch64']
22+
region:
23+
description: 'AWS region'
24+
required: true
25+
default: 'ALL'
26+
type: choice
27+
# Only non opt-in regions included for now
28+
options: ['ALL', 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'ap-south-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3', 'ap-southeast-1', 'ap-southeast-2', 'ca-central-1', 'eu-central-1', 'eu-west-1', 'eu-west-2', 'eu-west-3', 'eu-north-1', 'sa-east-1']
29+
confirm:
30+
description: 'Type "Yank Lambda Layer {layer version}" to confirm yanking the layer'
31+
required: true
32+
type: string
33+
34+
jobs:
35+
yank-layer:
36+
runs-on: ubuntu-latest
37+
continue-on-error: true
38+
strategy:
39+
fail-fast: false
40+
matrix:
41+
python-version: ${{ inputs.python_version == 'ALL' && fromJson('["3.10", "3.11", "3.12", "3.13"]') || fromJson(format('["{0}"]', inputs.python_version)) }}
42+
architecture: ${{ inputs.architecture == 'ALL' && fromJson('["x86_64", "aarch64"]') || fromJson(format('["{0}"]', inputs.architecture)) }}
43+
region: ${{ inputs.region == 'ALL' && fromJson('["us-east-1", "us-east-2", "us-west-1", "us-west-2", "ap-south-1", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "sa-east-1"]') || fromJson(format('["{0}"]', inputs.region)) }}
44+
45+
permissions:
46+
id-token: write
47+
48+
steps:
49+
- name: Validate confirmation
50+
run: |
51+
CONFIRM="${{ inputs.confirm }}"
52+
EXPECTED="Yank Lambda Layer ${{ inputs.layer_version }}"
53+
if [ "$CONFIRM" != "$EXPECTED" ]; then
54+
echo "Confirmation failed. You must type exactly '$EXPECTED' to proceed."
55+
exit 1
56+
fi
57+
echo "Confirmation validated"
58+
59+
- name: Configure AWS credentials
60+
uses: aws-actions/configure-aws-credentials@v4
61+
with:
62+
role-to-assume: ${{ secrets.STRANDS_LAMBDA_LAYER_PUBLISHER_ROLE }}
63+
aws-region: ${{ matrix.region }}
64+
65+
- name: Yank layer
66+
run: |
67+
PYTHON_VERSION="${{ matrix.python-version }}"
68+
ARCH="${{ matrix.architecture }}"
69+
REGION="${{ matrix.region }}"
70+
LAYER_NAME="strands-agents-py${PYTHON_VERSION//./_}-${ARCH}"
71+
LAYER_VERSION="${{ inputs.layer_version }}"
72+
73+
echo "Attempting to yank layer $LAYER_NAME version $LAYER_VERSION in region $REGION"
74+
75+
# Delete the layer version completely
76+
aws lambda delete-layer-version \
77+
--layer-name $LAYER_NAME \
78+
--version-number $LAYER_VERSION \
79+
--region "$REGION"
80+
81+
echo "Completed yank attempt for layer $LAYER_NAME version $LAYER_VERSION in region $REGION"

0 commit comments

Comments
 (0)