Skip to content

Commit 04b6231

Browse files
whummerclaude
andcommitted
Add AWS Community Day workshop: Order Processing Pipeline
Moves previous EuroPython workshop content to old/, and introduces a new 3-hour workshop built around a single serverless order processing app (API GW → Lambda → DynamoDB + SQS → Lambda → S3 + DLQ), covering: - Module 00: environment setup with dynamic auth token fetch - Module 01: awslocal CLI basics + full Terraform deployment - Module 02: pytest end-to-end integration tests - Module 03: Lambda live debugging via VS Code AWS Toolkit - Module 04: chaos engineering with DDB fault injection and DLQ replay - Module 05: App Inspector topology and request tracing - Module 06: AI integration via LocalStack MCP server and Claude Code skills Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7f94991 commit 04b6231

120 files changed

Lines changed: 22510 additions & 64 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.devcontainer/Dockerfile.github

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
FROM mcr.microsoft.com/devcontainers/universal
22

3-
RUN sudo apt update
3+
RUN sudo apt-get update && sudo apt-get install -y curl jq
44

5-
# install LocalStack CLI, awslocal, samlocal, tflocal
6-
# FIXME remove once https://github.com/yaml/pyyaml/issues/601 is fixed
7-
RUN pip install "setuptools" "wheel"
8-
RUN pip install --no-build-isolation "Cython<3.0" "pyyaml<5.5"
9-
RUN pip install localstack awscli-local[ver1] aws-sam-cli-local terraform-local
10-
RUN pip install --pre --upgrade localstack localstack-ext
11-
12-
# install CDK utils
13-
RUN npm install -g aws-cdk-local aws-cdk
5+
# Install LocalStack CLI and AWS/Terraform local wrappers
6+
RUN pip install localstack awscli-local terraform-local pytest requests
147

158
# default environment variables
169
ENV DEBUG=1
1710
ENV OVERRIDE_IN_DOCKER=0
1811
ENV EXTRA_CORS_ALLOWED_ORIGINS='*'
1912
ENV DISABLE_CUSTOM_CORS_APIGATEWAY=1
20-
ENV PORTS_CHECK_DOCKER_IMAGE=localstack/localstack-pro
2113

22-
# set test environment variables
14+
# Workshop token URL — set by organizer before each event.
15+
# The setup script fetches the actual token from this endpoint.
16+
ENV WORKSHOP_TOKEN_URL=""
17+
18+
# Standard LocalStack test credentials
2319
ENV AWS_ACCESS_KEY_ID=test
2420
ENV AWS_SECRET_ACCESS_KEY=test
21+
ENV AWS_DEFAULT_REGION=us-east-1

.devcontainer/devcontainer.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@
33
"dockerfile": "Dockerfile.github"
44
},
55
"customizations": {
6+
"vscode": {
7+
"extensions": [
8+
"amazonwebservices.aws-toolkit-vscode",
9+
"hashicorp.terraform"
10+
]
11+
},
612
"codespaces": {
713
"openFiles": [
814
"README.md"
915
]
1016
}
11-
}
17+
},
18+
"postStartCommand": "echo 'Run ./00-setup/setup.sh to configure your auth token and start LocalStack.'"
1219
}

.gitignore

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
.idea/
22
node_modules/
33
.venv/
4-
54
.DS_Store
5+
6+
# Terraform
7+
**/.terraform/
8+
**/.terraform.lock.hcl
9+
**/terraform.tfstate
10+
**/terraform.tfstate.backup
11+
01-serverless-app/terraform/.build/*.zip
12+
13+
# Python
14+
__pycache__/
15+
*.pyc
16+
.pytest_cache/

.vscode/launch.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Debug order-handler (LocalStack)",
6+
"type": "aws-sam",
7+
"request": "direct-invoke",
8+
"invokeTarget": {
9+
"target": "code",
10+
"lambdaHandler": "handler.handler",
11+
"projectRoot": "${workspaceFolder}/01-serverless-app/lambdas/order_handler"
12+
},
13+
"lambda": {
14+
"runtime": "python3.12",
15+
"payload": {
16+
"json": {
17+
"body": "{\"item\": \"debug-test\", \"quantity\": 1}"
18+
}
19+
},
20+
"environmentVariables": {
21+
"ORDERS_TABLE": "orders",
22+
"ORDERS_QUEUE_URL": "http://localhost:4566/000000000000/orders-queue",
23+
"AWS_ENDPOINT_URL": "http://localhost:4566"
24+
}
25+
},
26+
"sam": {
27+
"localArguments": ["--debug-port", "5890"]
28+
}
29+
}
30+
]
31+
}

00-setup/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Module 00 — Setup
2+
3+
Verify your environment and start LocalStack before proceeding to module 01.
4+
5+
---
6+
7+
## 1. Auth Token
8+
9+
Run the setup script — it fetches today's workshop token automatically:
10+
11+
```bash
12+
./00-setup/setup.sh
13+
```
14+
15+
If you're running this outside the workshop, set your own token:
16+
17+
```bash
18+
export LOCALSTACK_AUTH_TOKEN=<your-token>
19+
```
20+
21+
---
22+
23+
## 2. Start LocalStack
24+
25+
```bash
26+
localstack start -d
27+
```
28+
29+
Wait until you see `Ready.` in the logs (`localstack logs -f`).
30+
31+
---
32+
33+
## 3. Verify
34+
35+
```bash
36+
awslocal s3 ls # should return empty list (no error)
37+
curl -s http://localhost:4566/_localstack/health | python3 -m json.tool
38+
```
39+
40+
You should see `"running": true` and a list of available services.
41+
42+
---
43+
44+
## Installed Tools Checklist
45+
46+
| Tool | Purpose | Check |
47+
|------|---------|-------|
48+
| `localstack` | Run AWS locally | `localstack --version` |
49+
| `awslocal` | AWS CLI → LocalStack | `awslocal --version` |
50+
| `tflocal` | Terraform → LocalStack | `tflocal --version` |
51+
| `pytest` | Integration tests | `pytest --version` |
52+
| Docker | LocalStack runtime | `docker info` |
53+
54+
---
55+
56+
Next: [Module 01 — Serverless App](../01-serverless-app/README.md)

00-setup/setup.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Workshop auth token endpoint — organizer publishes token here on event day.
5+
# The URL points to a time-limited S3 object; replace before each workshop.
6+
TOKEN_URL="${WORKSHOP_TOKEN_URL:-}"
7+
8+
if [ -n "$TOKEN_URL" ]; then
9+
echo "Fetching LocalStack auth token from workshop endpoint..."
10+
TOKEN=$(curl -fsSL "$TOKEN_URL")
11+
export LOCALSTACK_AUTH_TOKEN="$TOKEN"
12+
echo "LOCALSTACK_AUTH_TOKEN=$TOKEN" >> "${HOME}/.bashrc"
13+
echo "LOCALSTACK_AUTH_TOKEN=$TOKEN" >> "${HOME}/.zshrc" 2>/dev/null || true
14+
echo "Token set."
15+
elif [ -z "${LOCALSTACK_AUTH_TOKEN:-}" ]; then
16+
echo "No token found. Set LOCALSTACK_AUTH_TOKEN or WORKSHOP_TOKEN_URL."
17+
echo " export LOCALSTACK_AUTH_TOKEN=<your-token>"
18+
exit 1
19+
else
20+
echo "Using existing LOCALSTACK_AUTH_TOKEN."
21+
fi
22+
23+
echo ""
24+
echo "Starting LocalStack..."
25+
localstack start -d
26+
27+
echo ""
28+
echo "Waiting for LocalStack to be ready..."
29+
localstack wait -t 60
30+
31+
echo ""
32+
echo "Verifying..."
33+
awslocal s3 ls > /dev/null && echo "OK: awslocal connected"
34+
curl -sf http://localhost:4566/_localstack/health | python3 -m json.tool | grep -q '"running"' && echo "OK: health check passed"
35+
36+
echo ""
37+
echo "Setup complete. Proceed to module 01."

01-serverless-app/README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Module 01 — Serverless App
2+
3+
Build intuition with the `awslocal` CLI, then deploy the full Order Processing Pipeline with Terraform.
4+
5+
---
6+
7+
## Part A — AWS CLI Basics with `awslocal`
8+
9+
### S3
10+
11+
```bash
12+
# Create a bucket and upload a file
13+
awslocal s3 mb s3://workshop-receipts
14+
echo "hello localstack" > /tmp/test.txt
15+
awslocal s3 cp /tmp/test.txt s3://workshop-receipts/test.txt
16+
awslocal s3 ls s3://workshop-receipts
17+
```
18+
19+
### DynamoDB
20+
21+
```bash
22+
# Create a table and put an item
23+
awslocal dynamodb create-table \
24+
--table-name orders-scratch \
25+
--attribute-definitions AttributeName=order_id,AttributeType=S \
26+
--key-schema AttributeName=order_id,KeyType=HASH \
27+
--billing-mode PAY_PER_REQUEST
28+
29+
awslocal dynamodb put-item \
30+
--table-name orders-scratch \
31+
--item '{"order_id": {"S": "ord-001"}, "status": {"S": "pending"}}'
32+
33+
awslocal dynamodb scan --table-name orders-scratch
34+
```
35+
36+
### SQS
37+
38+
```bash
39+
# Create a queue and send a message
40+
awslocal sqs create-queue --queue-name workshop-queue
41+
QUEUE_URL=$(awslocal sqs get-queue-url --queue-name workshop-queue --query QueueUrl --output text)
42+
awslocal sqs send-message --queue-url $QUEUE_URL --message-body '{"test": true}'
43+
awslocal sqs receive-message --queue-url $QUEUE_URL
44+
```
45+
46+
### Lambda (quick smoke test)
47+
48+
```bash
49+
# Invoke the order_handler directly (deployed in Part B)
50+
awslocal lambda invoke \
51+
--function-name order-handler \
52+
--payload '{"body": "{\"item\": \"book\", \"quantity\": 2}"}' \
53+
/tmp/response.json
54+
cat /tmp/response.json
55+
```
56+
57+
---
58+
59+
## Part B — Deploy with Terraform
60+
61+
```bash
62+
cd 01-serverless-app/terraform
63+
64+
# Initialize and deploy
65+
tflocal init
66+
tflocal apply -auto-approve
67+
68+
# Grab the API Gateway endpoint
69+
tflocal output api_endpoint
70+
```
71+
72+
### Test the deployed API
73+
74+
```bash
75+
API=$(tflocal output -raw api_endpoint)
76+
77+
# Create an order
78+
curl -s -X POST "$API/orders" \
79+
-H "Content-Type: application/json" \
80+
-d '{"item": "LocalStack T-Shirt", "quantity": 1}' | python3 -m json.tool
81+
82+
# Check DynamoDB
83+
awslocal dynamodb scan --table-name orders
84+
85+
# Check S3 receipts
86+
awslocal s3 ls s3://order-receipts/
87+
```
88+
89+
---
90+
91+
## What Got Deployed
92+
93+
```
94+
API Gateway → Lambda: order_handler → DynamoDB (orders table)
95+
→ SQS (orders-queue)
96+
└→ Lambda: order_processor
97+
→ DynamoDB (status update)
98+
→ S3 (receipt upload)
99+
SQS DLQ ← (on processor failure)
100+
```
101+
102+
---
103+
104+
Next: [Module 02 — E2E Testing](../02-e2e-testing/README.md)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import json
2+
import os
3+
import uuid
4+
import boto3
5+
6+
dynamodb = boto3.resource("dynamodb", endpoint_url=os.environ.get("AWS_ENDPOINT_URL"))
7+
sqs = boto3.client("sqs", endpoint_url=os.environ.get("AWS_ENDPOINT_URL"))
8+
9+
TABLE_NAME = os.environ["ORDERS_TABLE"]
10+
QUEUE_URL = os.environ["ORDERS_QUEUE_URL"]
11+
12+
13+
def handler(event, context):
14+
body = json.loads(event.get("body") or "{}")
15+
order_id = str(uuid.uuid4())
16+
17+
order = {
18+
"order_id": order_id,
19+
"item": body.get("item", "unknown"),
20+
"quantity": int(body.get("quantity", 1)),
21+
"status": "pending",
22+
}
23+
24+
table = dynamodb.Table(TABLE_NAME)
25+
table.put_item(Item=order)
26+
27+
sqs.send_message(
28+
QueueUrl=QUEUE_URL,
29+
MessageBody=json.dumps({"order_id": order_id, **order}),
30+
)
31+
32+
return {
33+
"statusCode": 201,
34+
"body": json.dumps({"order_id": order_id, "status": "pending"}),
35+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import json
2+
import os
3+
import boto3
4+
5+
dynamodb = boto3.resource("dynamodb", endpoint_url=os.environ.get("AWS_ENDPOINT_URL"))
6+
s3 = boto3.client("s3", endpoint_url=os.environ.get("AWS_ENDPOINT_URL"))
7+
8+
TABLE_NAME = os.environ["ORDERS_TABLE"]
9+
RECEIPTS_BUCKET = os.environ["RECEIPTS_BUCKET"]
10+
11+
12+
def handler(event, context):
13+
for record in event["Records"]:
14+
order = json.loads(record["body"])
15+
order_id = order["order_id"]
16+
17+
# Update order status in DynamoDB
18+
table = dynamodb.Table(TABLE_NAME)
19+
table.update_item(
20+
Key={"order_id": order_id},
21+
UpdateExpression="SET #s = :s",
22+
ExpressionAttributeNames={"#s": "status"},
23+
ExpressionAttributeValues={":s": "processed"},
24+
)
25+
26+
# Store receipt in S3
27+
receipt = {
28+
"order_id": order_id,
29+
"item": order.get("item"),
30+
"quantity": order.get("quantity"),
31+
"status": "processed",
32+
}
33+
s3.put_object(
34+
Bucket=RECEIPTS_BUCKET,
35+
Key=f"receipts/{order_id}.json",
36+
Body=json.dumps(receipt),
37+
ContentType="application/json",
38+
)

01-serverless-app/terraform/.build/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)