Skip to content

Commit 73d9114

Browse files
authored
feat: update SAM template automatically (#128)
1 parent fea8882 commit 73d9114

File tree

5 files changed

+331
-77
lines changed

5 files changed

+331
-77
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Update SAM Template
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "examples/**"
7+
8+
permissions:
9+
contents: write
10+
11+
concurrency:
12+
group: ${{ github.head_ref }}-${{ github.run_id}}-sam-template
13+
cancel-in-progress: true
14+
15+
jobs:
16+
update-template:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
with:
21+
token: ${{ secrets.GITHUB_TOKEN }}
22+
ref: ${{ github.head_ref }}
23+
24+
- name: Set up Python 3.13
25+
uses: actions/setup-python@v5
26+
with:
27+
python-version: "3.13"
28+
29+
- name: Install PyYAML
30+
run: pip install PyYAML
31+
32+
- name: Generate SAM template
33+
run: python examples/scripts/generate_sam_template.py
34+
35+
- name: Commit and push changes
36+
run: |
37+
git config --local user.email "action@github.com"
38+
git config --local user.name "GitHub Action"
39+
git add .
40+
if git diff --staged --quiet; then
41+
echo "No changes to commit"
42+
else
43+
git commit -m "chore: update SAM template" --no-verify
44+
git push
45+
fi

examples/cli.py

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -215,59 +215,6 @@ def bootstrap_account():
215215
return True
216216

217217

218-
def generate_sam_template(*, skip_durable_config=False):
219-
"""Generate SAM template for all examples."""
220-
catalog = load_catalog()
221-
222-
template = {
223-
"AWSTemplateFormatVersion": "2010-09-09",
224-
"Transform": "AWS::Serverless-2016-10-31",
225-
"Globals": {
226-
"Function": {
227-
"Runtime": "python3.13",
228-
"Timeout": 60,
229-
"MemorySize": 128,
230-
"Environment": {
231-
"Variables": {"AWS_ENDPOINT_URL_LAMBDA": {"Ref": "LambdaEndpoint"}}
232-
},
233-
}
234-
},
235-
"Parameters": {
236-
"LambdaEndpoint": {
237-
"Type": "String",
238-
"Default": "https://lambda.us-west-2.amazonaws.com",
239-
}
240-
},
241-
"Resources": {},
242-
}
243-
244-
for example in catalog["examples"]:
245-
# Convert handler name to PascalCase (e.g., hello_world -> HelloWorld)
246-
handler_base = example["handler"].replace(".handler", "")
247-
function_name = "".join(word.capitalize() for word in handler_base.split("_"))
248-
template["Resources"][function_name] = {
249-
"Type": "AWS::Serverless::Function",
250-
"Properties": {
251-
"CodeUri": "build/",
252-
"Handler": example["handler"],
253-
"Description": example["description"],
254-
},
255-
}
256-
257-
if not skip_durable_config and "durableConfig" in example:
258-
template["Resources"][function_name]["Properties"]["DurableConfig"] = (
259-
example["durableConfig"]
260-
)
261-
262-
import yaml
263-
264-
template_path = Path(__file__).parent / "template.yaml"
265-
with open(template_path, "w") as f:
266-
yaml.dump(template, f, default_flow_style=False, sort_keys=False)
267-
268-
return True
269-
270-
271218
def create_deployment_package(example_name: str) -> Path:
272219
"""Create deployment package for example."""
273220

@@ -484,16 +431,6 @@ def main():
484431
# List command
485432
subparsers.add_parser("list", help="List available examples")
486433

487-
# SAM template command
488-
sam_parser = subparsers.add_parser(
489-
"sam", help="Generate SAM template for all examples"
490-
)
491-
sam_parser.add_argument(
492-
"--skip-durable-config",
493-
action="store_true",
494-
help="Skip adding DurableConfig properties to functions",
495-
)
496-
497434
# Deploy command
498435
deploy_parser = subparsers.add_parser("deploy", help="Deploy an example")
499436
deploy_parser.add_argument("example_name", help="Name of example to deploy")
@@ -529,8 +466,6 @@ def main():
529466
build_examples()
530467
elif args.command == "list":
531468
list_examples()
532-
elif args.command == "sam":
533-
generate_sam_template(skip_durable_config=args.skip_durable_config)
534469
elif args.command == "deploy":
535470
deploy_function(args.example_name, args.function_name)
536471
elif args.command == "invoke":
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env python3
2+
3+
import json
4+
from pathlib import Path
5+
6+
import yaml
7+
8+
9+
def load_catalog():
10+
"""Load examples catalog."""
11+
catalog_path = Path(__file__).parent.parent / "examples-catalog.json"
12+
with open(catalog_path) as f:
13+
return json.load(f)
14+
15+
16+
def generate_sam_template():
17+
"""Generate SAM template for all examples."""
18+
catalog = load_catalog()
19+
20+
template = {
21+
"AWSTemplateFormatVersion": "2010-09-09",
22+
"Transform": "AWS::Serverless-2016-10-31",
23+
"Globals": {
24+
"Function": {
25+
"Runtime": "python3.13",
26+
"Timeout": 60,
27+
"MemorySize": 128,
28+
"Environment": {
29+
"Variables": {"AWS_ENDPOINT_URL_LAMBDA": {"Ref": "LambdaEndpoint"}}
30+
},
31+
}
32+
},
33+
"Parameters": {
34+
"LambdaEndpoint": {
35+
"Type": "String",
36+
"Default": "https://lambda.us-west-2.amazonaws.com",
37+
}
38+
},
39+
"Resources": {
40+
"DurableFunctionRole": {
41+
"Type": "AWS::IAM::Role",
42+
"Properties": {
43+
"AssumeRolePolicyDocument": {
44+
"Version": "2012-10-17",
45+
"Statement": [
46+
{
47+
"Effect": "Allow",
48+
"Principal": {"Service": "lambda.amazonaws.com"},
49+
"Action": "sts:AssumeRole",
50+
}
51+
],
52+
},
53+
"ManagedPolicyArns": [
54+
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
55+
],
56+
"Policies": [
57+
{
58+
"PolicyName": "DurableExecutionPolicy",
59+
"PolicyDocument": {
60+
"Version": "2012-10-17",
61+
"Statement": [
62+
{
63+
"Effect": "Allow",
64+
"Action": [
65+
"lambda:CheckpointDurableExecution",
66+
"lambda:GetDurableExecutionState",
67+
],
68+
"Resource": "*",
69+
}
70+
],
71+
},
72+
}
73+
],
74+
},
75+
}
76+
},
77+
}
78+
79+
for example in catalog["examples"]:
80+
# Convert handler name to PascalCase (e.g., hello_world -> HelloWorld)
81+
handler_base = example["handler"].replace(".handler", "")
82+
function_name = "".join(word.capitalize() for word in handler_base.split("_"))
83+
template["Resources"][function_name] = {
84+
"Type": "AWS::Serverless::Function",
85+
"Properties": {
86+
"CodeUri": "build/",
87+
"Handler": example["handler"],
88+
"Description": example["description"],
89+
"Role": {"Fn::GetAtt": ["DurableFunctionRole", "Arn"]},
90+
},
91+
}
92+
93+
if "durableConfig" in example:
94+
template["Resources"][function_name]["Properties"]["DurableConfig"] = (
95+
example["durableConfig"]
96+
)
97+
98+
template_path = Path(__file__).parent.parent / "template.yaml"
99+
with open(template_path, "w") as f:
100+
yaml.dump(template, f, default_flow_style=False, sort_keys=False)
101+
102+
print(f"Generated SAM template at {template_path}")
103+
104+
105+
if __name__ == "__main__":
106+
generate_sam_template()

0 commit comments

Comments
 (0)