Add visitor counter with DynamoDB
m
matthew.dempsky@tonal.com
·
3 days ago
Changes
168
added
13
removed
5
files
Description
Each request atomically increments a counter in DynamoDB and returns 'hello visitor #N\!'. Adds a PAY_PER_REQUEST table and minimal IAM permissions.
Details
- Branches
- visitor-counter → main
- Commits
- base 3b66d3a → head 2191ef6
- Last activity
- 3 days ago
- Merge
- SQUASH_MERGE by matthew.dempsky@tonal.com (4b8aa2c)
- Approval
- Overridden
Timeline
Opened
by matthew.dempsky@tonal.com
38efd3c
3 days ago
Pushed
by matthew.dempsky@tonal.com
38efd3c → 523a496
3 days ago
Pushed
by matthew.dempsky@tonal.com
523a496 → 2191ef6
3 days ago
Approval overridden
by matthew.dempsky@tonal.com
3 days ago
Squash merged
by matthew.dempsky@tonal.com
3 days ago
Files Changed
M buildspec.yml
| @@ -3,6 +3,7 @@ | ||
| 3 | 3 | env: |
| 4 | 4 | variables: |
| 5 | 5 | ENV: "main" |
| 6 | ACTION: "apply" | |
| 6 | 7 | |
| 7 | 8 | phases: |
| 8 | 9 | install: |
| @@ -16,21 +17,30 @@ | ||
| 16 | 17 | build: |
| 17 | 18 | commands: |
| 18 | 19 | # Compile a static Linux binary named "bootstrap" — the name the |
| 19 | # provided.al2023 Lambda runtime expects. | |
| 20 | - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -tags lambda.norpc -o bootstrap main.go | |
| 21 | - zip function.zip bootstrap | |
| 20 | # provided.al2023 Lambda runtime expects. Skipped for destroy builds. | |
| 21 | - | | |
| 22 | if [ "$ACTION" = "apply" ]; then | |
| 23 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -tags lambda.norpc -o bootstrap main.go | |
| 24 | zip function.zip bootstrap | |
| 25 | fi | |
| 22 | 26 | |
| 23 | # Initialize Terraform with the S3 backend. ACCOUNT_ID is set by the | |
| 24 | # CodeBuild environment; ENV comes from the env block above (overridden | |
| 25 | # per-build for PR environments). | |
| 27 | # Initialize Terraform with the S3 backend. ACCOUNT_ID comes from | |
| 28 | # STS; ENV and ACTION are set by EventBridge overrides for PR builds. | |
| 26 | 29 | - ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) |
| 27 | 30 | - >- |
| 28 | 31 | terraform init |
| 29 | 32 | -backend-config="bucket=bakery-v6-artifacts-${ACCOUNT_ID}" |
| 30 | 33 | -backend-config="key=state/${ENV}/terraform.tfstate" |
| 31 | 34 | |
| 32 | - terraform apply -auto-approve -var="env=${ENV}" | |
| 35 | # For destroy builds (PR closed/merged), tear down the environment. | |
| 36 | # For apply builds, create or update it. | |
| 37 | - | | |
| 38 | if [ "$ACTION" = "destroy" ]; then | |
| 39 | terraform destroy -auto-approve -var="env=${ENV}" | |
| 40 | else | |
| 41 | terraform apply -auto-approve -var="env=${ENV}" | |
| 42 | fi | |
| 33 | 43 | |
| 34 | 44 | post_build: |
| 35 | 45 | commands: |
| 36 | - terraform output -json | |
| 46 | - terraform output -json || true | |
| @@ -3,6 +3,7 @@ | |||
| 3 | env: | 3 | env: |
| 4 | variables: | 4 | variables: |
| 5 | ENV: "main" | 5 | ENV: "main" |
| 6 | ACTION: "apply" | ||
| 6 | 7 | ||
| 7 | phases: | 8 | phases: |
| 8 | install: | 9 | install: |
| @@ -16,21 +17,30 @@ | |||
| 16 | build: | 17 | build: |
| 17 | commands: | 18 | commands: |
| 18 | # Compile a static Linux binary named "bootstrap" — the name the | 19 | # Compile a static Linux binary named "bootstrap" — the name the |
| 19 | # provided.al2023 Lambda runtime expects. | 20 | # provided.al2023 Lambda runtime expects. Skipped for destroy builds. |
| 20 | - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -tags lambda.norpc -o bootstrap main.go | 21 | - | |
| 21 | - zip function.zip bootstrap | 22 | if [ "$ACTION" = "apply" ]; then |
| 23 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -tags lambda.norpc -o bootstrap main.go | ||
| 24 | zip function.zip bootstrap | ||
| 25 | fi | ||
| 22 | 26 | ||
| 23 | # Initialize Terraform with the S3 backend. ACCOUNT_ID is set by the | 27 | # Initialize Terraform with the S3 backend. ACCOUNT_ID comes from |
| 24 | # CodeBuild environment; ENV comes from the env block above (overridden | 28 | # STS; ENV and ACTION are set by EventBridge overrides for PR builds. |
| 25 | # per-build for PR environments). | ||
| 26 | - ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) | 29 | - ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) |
| 27 | - >- | 30 | - >- |
| 28 | terraform init | 31 | terraform init |
| 29 | -backend-config="bucket=bakery-v6-artifacts-${ACCOUNT_ID}" | 32 | -backend-config="bucket=bakery-v6-artifacts-${ACCOUNT_ID}" |
| 30 | -backend-config="key=state/${ENV}/terraform.tfstate" | 33 | -backend-config="key=state/${ENV}/terraform.tfstate" |
| 31 | 34 | ||
| 32 | - terraform apply -auto-approve -var="env=${ENV}" | 35 | # For destroy builds (PR closed/merged), tear down the environment. |
| 36 | # For apply builds, create or update it. | ||
| 37 | - | | ||
| 38 | if [ "$ACTION" = "destroy" ]; then | ||
| 39 | terraform destroy -auto-approve -var="env=${ENV}" | ||
| 40 | else | ||
| 41 | terraform apply -auto-approve -var="env=${ENV}" | ||
| 42 | fi | ||
| 33 | 43 | ||
| 34 | post_build: | 44 | post_build: |
| 35 | commands: | 45 | commands: |
| 36 | - terraform output -json | 46 | - terraform output -json || true |
M go.mod
| @@ -2,4 +2,25 @@ | ||
| 2 | 2 | |
| 3 | 3 | go 1.25.7 |
| 4 | 4 | |
| 5 | require github.com/aws/aws-lambda-go v1.52.0 | |
| 5 | require ( | |
| 6 | github.com/aws/aws-lambda-go v1.52.0 | |
| 7 | github.com/aws/aws-sdk-go-v2 v1.41.1 | |
| 8 | github.com/aws/aws-sdk-go-v2/config v1.32.7 | |
| 9 | github.com/aws/aws-sdk-go-v2/service/dynamodb v1.55.0 | |
| 10 | ) | |
| 11 | ||
| 12 | require ( | |
| 13 | github.com/aws/aws-sdk-go-v2/credentials v1.19.7 // indirect | |
| 14 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect | |
| 15 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect | |
| 16 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect | |
| 17 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect | |
| 18 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect | |
| 19 | github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.17 // indirect | |
| 20 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect | |
| 21 | github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect | |
| 22 | github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect | |
| 23 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 // indirect | |
| 24 | github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect | |
| 25 | github.com/aws/smithy-go v1.24.0 // indirect | |
| 26 | ) | |
| @@ -2,4 +2,25 @@ | |||
| 2 | 2 | ||
| 3 | go 1.25.7 | 3 | go 1.25.7 |
| 4 | 4 | ||
| 5 | require github.com/aws/aws-lambda-go v1.52.0 | 5 | require ( |
| 6 | github.com/aws/aws-lambda-go v1.52.0 | ||
| 7 | github.com/aws/aws-sdk-go-v2 v1.41.1 | ||
| 8 | github.com/aws/aws-sdk-go-v2/config v1.32.7 | ||
| 9 | github.com/aws/aws-sdk-go-v2/service/dynamodb v1.55.0 | ||
| 10 | ) | ||
| 11 | |||
| 12 | require ( | ||
| 13 | github.com/aws/aws-sdk-go-v2/credentials v1.19.7 // indirect | ||
| 14 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect | ||
| 15 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect | ||
| 16 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect | ||
| 17 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect | ||
| 18 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect | ||
| 19 | github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.17 // indirect | ||
| 20 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect | ||
| 21 | github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect | ||
| 22 | github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect | ||
| 23 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 // indirect | ||
| 24 | github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect | ||
| 25 | github.com/aws/smithy-go v1.24.0 // indirect | ||
| 26 | ) | ||
M go.sum
| @@ -1,5 +1,37 @@ | ||
| 1 | 1 | github.com/aws/aws-lambda-go v1.52.0 h1:5NfiRaVl9FafUIt2Ld/Bv22kT371mfAI+l1Hd+tV7ZE= |
| 2 | 2 | github.com/aws/aws-lambda-go v1.52.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= |
| 3 | github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= | |
| 4 | github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= | |
| 5 | github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY= | |
| 6 | github.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY= | |
| 7 | github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8= | |
| 8 | github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw= | |
| 9 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY= | |
| 10 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA= | |
| 11 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= | |
| 12 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= | |
| 13 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= | |
| 14 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= | |
| 15 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= | |
| 16 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= | |
| 17 | github.com/aws/aws-sdk-go-v2/service/dynamodb v1.55.0 h1:CyYoeHWjVSGimzMhlL0Z4l5gLCa++ccnRJKrsaNssxE= | |
| 18 | github.com/aws/aws-sdk-go-v2/service/dynamodb v1.55.0/go.mod h1:ctEsEHY2vFQc6i4KU07q4n68v7BAmTbujv2Y+z8+hQY= | |
| 19 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= | |
| 20 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= | |
| 21 | github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.17 h1:Nhx/OYX+ukejm9t/MkWI8sucnsiroNYNGb5ddI9ungQ= | |
| 22 | github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.17/go.mod h1:AjmK8JWnlAevq1b1NBtv5oQVG4iqnYXUufdgol+q9wg= | |
| 23 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY= | |
| 24 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU= | |
| 25 | github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y= | |
| 26 | github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M= | |
| 27 | github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk= | |
| 28 | github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw= | |
| 29 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds= | |
| 30 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= | |
| 31 | github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ= | |
| 32 | github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ= | |
| 33 | github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= | |
| 34 | github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= | |
| 3 | 35 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
| 4 | 36 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
| 5 | 37 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
| @@ -1,5 +1,37 @@ | |||
| 1 | github.com/aws/aws-lambda-go v1.52.0 h1:5NfiRaVl9FafUIt2Ld/Bv22kT371mfAI+l1Hd+tV7ZE= | 1 | github.com/aws/aws-lambda-go v1.52.0 h1:5NfiRaVl9FafUIt2Ld/Bv22kT371mfAI+l1Hd+tV7ZE= |
| 2 | github.com/aws/aws-lambda-go v1.52.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= | 2 | github.com/aws/aws-lambda-go v1.52.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= |
| 3 | github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= | ||
| 4 | github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= | ||
| 5 | github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY= | ||
| 6 | github.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY= | ||
| 7 | github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8= | ||
| 8 | github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw= | ||
| 9 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY= | ||
| 10 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA= | ||
| 11 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= | ||
| 12 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= | ||
| 13 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= | ||
| 14 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= | ||
| 15 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= | ||
| 16 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= | ||
| 17 | github.com/aws/aws-sdk-go-v2/service/dynamodb v1.55.0 h1:CyYoeHWjVSGimzMhlL0Z4l5gLCa++ccnRJKrsaNssxE= | ||
| 18 | github.com/aws/aws-sdk-go-v2/service/dynamodb v1.55.0/go.mod h1:ctEsEHY2vFQc6i4KU07q4n68v7BAmTbujv2Y+z8+hQY= | ||
| 19 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= | ||
| 20 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= | ||
| 21 | github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.17 h1:Nhx/OYX+ukejm9t/MkWI8sucnsiroNYNGb5ddI9ungQ= | ||
| 22 | github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.17/go.mod h1:AjmK8JWnlAevq1b1NBtv5oQVG4iqnYXUufdgol+q9wg= | ||
| 23 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY= | ||
| 24 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU= | ||
| 25 | github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y= | ||
| 26 | github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M= | ||
| 27 | github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk= | ||
| 28 | github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw= | ||
| 29 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds= | ||
| 30 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= | ||
| 31 | github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ= | ||
| 32 | github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ= | ||
| 33 | github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= | ||
| 34 | github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= | ||
| 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | 35 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
| 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 36 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
| 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | 37 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
M infra.tf
| @@ -72,8 +72,17 @@ | ||
| 72 | 72 | default = "main" |
| 73 | 73 | } |
| 74 | 74 | |
| 75 | data "aws_caller_identity" "current" {} | |
| 76 | ||
| 75 | 77 | locals { |
| 76 | 78 | prefix = "bakery-v6-${var.env}" |
| 79 | ||
| 80 | # Derive the permissions boundary ARN from convention. The ops account | |
| 81 | # creates one boundary per deployer type (main vs prs). Any roles we | |
| 82 | # create must have this boundary attached — the deployer's IAM policy | |
| 83 | # enforces it, and the boundary caps effective permissions to our prefix. | |
| 84 | deployer_type = startswith(var.env, "pr-") ? "prs" : "main" | |
| 85 | boundary_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/bakery-v6-deployer-${local.deployer_type}-boundary" | |
| 77 | 86 | } |
| 78 | 87 | |
| 79 | 88 | |
| @@ -87,11 +96,11 @@ | ||
| 87 | 96 | # there are no shared library dependencies to worry about. |
| 88 | 97 | |
| 89 | 98 | # The Lambda execution role is the identity the function assumes at runtime. |
| 90 | # It needs basic permissions to write CloudWatch logs. Add more policies | |
| 91 | # here as the function grows (DynamoDB access, S3, etc.). | |
| 99 | # It needs CloudWatch logs and DynamoDB access for the visitor counter. | |
| 92 | 100 | |
| 93 | 101 | resource "aws_iam_role" "lambda_exec" { |
| 94 | name = "${local.prefix}-hello-exec" | |
| 102 | name = "${local.prefix}-hello-exec" | |
| 103 | permissions_boundary = local.boundary_arn | |
| 95 | 104 | |
| 96 | 105 | assume_role_policy = jsonencode({ |
| 97 | 106 | Version = "2012-10-17" |
| @@ -108,6 +117,20 @@ | ||
| 108 | 117 | policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" |
| 109 | 118 | } |
| 110 | 119 | |
| 120 | resource "aws_iam_role_policy" "lambda_dynamodb" { | |
| 121 | name = "DynamoDBAccess" | |
| 122 | role = aws_iam_role.lambda_exec.id | |
| 123 | ||
| 124 | policy = jsonencode({ | |
| 125 | Version = "2012-10-17" | |
| 126 | Statement = [{ | |
| 127 | Effect = "Allow" | |
| 128 | Action = ["dynamodb:UpdateItem"] | |
| 129 | Resource = aws_dynamodb_table.visitors.arn | |
| 130 | }] | |
| 131 | }) | |
| 132 | } | |
| 133 | ||
| 111 | 134 | resource "aws_lambda_function" "hello" { |
| 112 | 135 | function_name = "${local.prefix}-hello" |
| 113 | 136 | role = aws_iam_role.lambda_exec.arn |
| @@ -118,6 +141,32 @@ | ||
| 118 | 141 | # Built by `go build` + `zip` before terraform apply (see buildspec.yml). |
| 119 | 142 | filename = "function.zip" |
| 120 | 143 | source_code_hash = filebase64sha256("function.zip") |
| 144 | ||
| 145 | environment { | |
| 146 | variables = { | |
| 147 | TABLE_NAME = aws_dynamodb_table.visitors.name | |
| 148 | } | |
| 149 | } | |
| 150 | } | |
| 151 | ||
| 152 | ||
| 153 | # ============================================================================ | |
| 154 | # DynamoDB table | |
| 155 | # ============================================================================ | |
| 156 | # | |
| 157 | # A simple table for tracking visitor count. Uses a single item with an | |
| 158 | # atomic counter — the Lambda does UpdateItem with ADD on each request. | |
| 159 | # PAY_PER_REQUEST so it costs nothing when idle. | |
| 160 | ||
| 161 | resource "aws_dynamodb_table" "visitors" { | |
| 162 | name = "${local.prefix}-visitors" | |
| 163 | billing_mode = "PAY_PER_REQUEST" | |
| 164 | hash_key = "pk" | |
| 165 | ||
| 166 | attribute { | |
| 167 | name = "pk" | |
| 168 | type = "S" | |
| 169 | } | |
| 121 | 170 | } |
| 122 | 171 | |
| 123 | 172 | |
| @@ -72,8 +72,17 @@ | |||
| 72 | default = "main" | 72 | default = "main" |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | data "aws_caller_identity" "current" {} | ||
| 76 | |||
| 75 | locals { | 77 | locals { |
| 76 | prefix = "bakery-v6-${var.env}" | 78 | prefix = "bakery-v6-${var.env}" |
| 79 | |||
| 80 | # Derive the permissions boundary ARN from convention. The ops account | ||
| 81 | # creates one boundary per deployer type (main vs prs). Any roles we | ||
| 82 | # create must have this boundary attached — the deployer's IAM policy | ||
| 83 | # enforces it, and the boundary caps effective permissions to our prefix. | ||
| 84 | deployer_type = startswith(var.env, "pr-") ? "prs" : "main" | ||
| 85 | boundary_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/bakery-v6-deployer-${local.deployer_type}-boundary" | ||
| 77 | } | 86 | } |
| 78 | 87 | ||
| 79 | 88 | ||
| @@ -87,11 +96,11 @@ | |||
| 87 | # there are no shared library dependencies to worry about. | 96 | # there are no shared library dependencies to worry about. |
| 88 | 97 | ||
| 89 | # The Lambda execution role is the identity the function assumes at runtime. | 98 | # The Lambda execution role is the identity the function assumes at runtime. |
| 90 | # It needs basic permissions to write CloudWatch logs. Add more policies | 99 | # It needs CloudWatch logs and DynamoDB access for the visitor counter. |
| 91 | # here as the function grows (DynamoDB access, S3, etc.). | ||
| 92 | 100 | ||
| 93 | resource "aws_iam_role" "lambda_exec" { | 101 | resource "aws_iam_role" "lambda_exec" { |
| 94 | name = "${local.prefix}-hello-exec" | 102 | name = "${local.prefix}-hello-exec" |
| 103 | permissions_boundary = local.boundary_arn | ||
| 95 | 104 | ||
| 96 | assume_role_policy = jsonencode({ | 105 | assume_role_policy = jsonencode({ |
| 97 | Version = "2012-10-17" | 106 | Version = "2012-10-17" |
| @@ -108,6 +117,20 @@ | |||
| 108 | policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" | 117 | policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" |
| 109 | } | 118 | } |
| 110 | 119 | ||
| 120 | resource "aws_iam_role_policy" "lambda_dynamodb" { | ||
| 121 | name = "DynamoDBAccess" | ||
| 122 | role = aws_iam_role.lambda_exec.id | ||
| 123 | |||
| 124 | policy = jsonencode({ | ||
| 125 | Version = "2012-10-17" | ||
| 126 | Statement = [{ | ||
| 127 | Effect = "Allow" | ||
| 128 | Action = ["dynamodb:UpdateItem"] | ||
| 129 | Resource = aws_dynamodb_table.visitors.arn | ||
| 130 | }] | ||
| 131 | }) | ||
| 132 | } | ||
| 133 | |||
| 111 | resource "aws_lambda_function" "hello" { | 134 | resource "aws_lambda_function" "hello" { |
| 112 | function_name = "${local.prefix}-hello" | 135 | function_name = "${local.prefix}-hello" |
| 113 | role = aws_iam_role.lambda_exec.arn | 136 | role = aws_iam_role.lambda_exec.arn |
| @@ -118,6 +141,32 @@ | |||
| 118 | # Built by `go build` + `zip` before terraform apply (see buildspec.yml). | 141 | # Built by `go build` + `zip` before terraform apply (see buildspec.yml). |
| 119 | filename = "function.zip" | 142 | filename = "function.zip" |
| 120 | source_code_hash = filebase64sha256("function.zip") | 143 | source_code_hash = filebase64sha256("function.zip") |
| 144 | |||
| 145 | environment { | ||
| 146 | variables = { | ||
| 147 | TABLE_NAME = aws_dynamodb_table.visitors.name | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | |||
| 153 | # ============================================================================ | ||
| 154 | # DynamoDB table | ||
| 155 | # ============================================================================ | ||
| 156 | # | ||
| 157 | # A simple table for tracking visitor count. Uses a single item with an | ||
| 158 | # atomic counter — the Lambda does UpdateItem with ADD on each request. | ||
| 159 | # PAY_PER_REQUEST so it costs nothing when idle. | ||
| 160 | |||
| 161 | resource "aws_dynamodb_table" "visitors" { | ||
| 162 | name = "${local.prefix}-visitors" | ||
| 163 | billing_mode = "PAY_PER_REQUEST" | ||
| 164 | hash_key = "pk" | ||
| 165 | |||
| 166 | attribute { | ||
| 167 | name = "pk" | ||
| 168 | type = "S" | ||
| 169 | } | ||
| 121 | } | 170 | } |
| 122 | 171 | ||
| 123 | 172 | ||
M main.go
| @@ -2,16 +2,59 @@ | ||
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "context" |
| 5 | "fmt" | |
| 6 | "os" | |
| 7 | "strconv" | |
| 5 | 8 | |
| 6 | 9 | "github.com/aws/aws-lambda-go/events" |
| 7 | 10 | "github.com/aws/aws-lambda-go/lambda" |
| 11 | "github.com/aws/aws-sdk-go-v2/aws" | |
| 12 | "github.com/aws/aws-sdk-go-v2/config" | |
| 13 | "github.com/aws/aws-sdk-go-v2/service/dynamodb" | |
| 14 | "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" | |
| 8 | 15 | ) |
| 9 | 16 | |
| 17 | var ddb *dynamodb.Client | |
| 18 | var tableName string | |
| 19 | ||
| 20 | func init() { | |
| 21 | tableName = os.Getenv("TABLE_NAME") | |
| 22 | cfg, err := config.LoadDefaultConfig(context.Background()) | |
| 23 | if err != nil { | |
| 24 | panic(err) | |
| 25 | } | |
| 26 | ddb = dynamodb.NewFromConfig(cfg) | |
| 27 | } | |
| 28 | ||
| 10 | 29 | func handler(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { |
| 30 | // Atomic increment: adds 1 to the counter and returns the new value. | |
| 31 | out, err := ddb.UpdateItem(ctx, &dynamodb.UpdateItemInput{ | |
| 32 | TableName: &tableName, | |
| 33 | Key: map[string]types.AttributeValue{ | |
| 34 | "pk": &types.AttributeValueMemberS{Value: "visitors"}, | |
| 35 | }, | |
| 36 | UpdateExpression: aws.String("ADD #n :inc"), | |
| 37 | ExpressionAttributeNames: map[string]string{ | |
| 38 | "#n": "count", | |
| 39 | }, | |
| 40 | ExpressionAttributeValues: map[string]types.AttributeValue{ | |
| 41 | ":inc": &types.AttributeValueMemberN{Value: "1"}, | |
| 42 | }, | |
| 43 | ReturnValues: types.ReturnValueUpdatedNew, | |
| 44 | }) | |
| 45 | if err != nil { | |
| 46 | return events.APIGatewayV2HTTPResponse{ | |
| 47 | StatusCode: 500, | |
| 48 | Body: fmt.Sprintf(`{"error":"%s"}`, err.Error()), | |
| 49 | }, nil | |
| 50 | } | |
| 51 | ||
| 52 | count, _ := strconv.Atoi(out.Attributes["count"].(*types.AttributeValueMemberN).Value) | |
| 53 | ||
| 11 | 54 | return events.APIGatewayV2HTTPResponse{ |
| 12 | 55 | StatusCode: 200, |
| 13 | 56 | Headers: map[string]string{"Content-Type": "application/json"}, |
| 14 | Body: `{"message":"hello world"}`, | |
| 57 | Body: fmt.Sprintf(`{"message":"hello visitor #%d!"}`, count), | |
| 15 | 58 | }, nil |
| 16 | 59 | } |
| 17 | 60 | |
| @@ -2,16 +2,59 @@ | |||
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "context" | 4 | "context" |
| 5 | "fmt" | ||
| 6 | "os" | ||
| 7 | "strconv" | ||
| 5 | 8 | ||
| 6 | "github.com/aws/aws-lambda-go/events" | 9 | "github.com/aws/aws-lambda-go/events" |
| 7 | "github.com/aws/aws-lambda-go/lambda" | 10 | "github.com/aws/aws-lambda-go/lambda" |
| 11 | "github.com/aws/aws-sdk-go-v2/aws" | ||
| 12 | "github.com/aws/aws-sdk-go-v2/config" | ||
| 13 | "github.com/aws/aws-sdk-go-v2/service/dynamodb" | ||
| 14 | "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" | ||
| 8 | ) | 15 | ) |
| 9 | 16 | ||
| 17 | var ddb *dynamodb.Client | ||
| 18 | var tableName string | ||
| 19 | |||
| 20 | func init() { | ||
| 21 | tableName = os.Getenv("TABLE_NAME") | ||
| 22 | cfg, err := config.LoadDefaultConfig(context.Background()) | ||
| 23 | if err != nil { | ||
| 24 | panic(err) | ||
| 25 | } | ||
| 26 | ddb = dynamodb.NewFromConfig(cfg) | ||
| 27 | } | ||
| 28 | |||
| 10 | func handler(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { | 29 | func handler(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { |
| 30 | // Atomic increment: adds 1 to the counter and returns the new value. | ||
| 31 | out, err := ddb.UpdateItem(ctx, &dynamodb.UpdateItemInput{ | ||
| 32 | TableName: &tableName, | ||
| 33 | Key: map[string]types.AttributeValue{ | ||
| 34 | "pk": &types.AttributeValueMemberS{Value: "visitors"}, | ||
| 35 | }, | ||
| 36 | UpdateExpression: aws.String("ADD #n :inc"), | ||
| 37 | ExpressionAttributeNames: map[string]string{ | ||
| 38 | "#n": "count", | ||
| 39 | }, | ||
| 40 | ExpressionAttributeValues: map[string]types.AttributeValue{ | ||
| 41 | ":inc": &types.AttributeValueMemberN{Value: "1"}, | ||
| 42 | }, | ||
| 43 | ReturnValues: types.ReturnValueUpdatedNew, | ||
| 44 | }) | ||
| 45 | if err != nil { | ||
| 46 | return events.APIGatewayV2HTTPResponse{ | ||
| 47 | StatusCode: 500, | ||
| 48 | Body: fmt.Sprintf(`{"error":"%s"}`, err.Error()), | ||
| 49 | }, nil | ||
| 50 | } | ||
| 51 | |||
| 52 | count, _ := strconv.Atoi(out.Attributes["count"].(*types.AttributeValueMemberN).Value) | ||
| 53 | |||
| 11 | return events.APIGatewayV2HTTPResponse{ | 54 | return events.APIGatewayV2HTTPResponse{ |
| 12 | StatusCode: 200, | 55 | StatusCode: 200, |
| 13 | Headers: map[string]string{"Content-Type": "application/json"}, | 56 | Headers: map[string]string{"Content-Type": "application/json"}, |
| 14 | Body: `{"message":"hello world"}`, | 57 | Body: fmt.Sprintf(`{"message":"hello visitor #%d!"}`, count), |
| 15 | }, nil | 58 | }, nil |
| 16 | } | 59 | } |
| 17 | 60 | ||