Skip to content

Commit 0c7e8e2

Browse files
moritzzimmerclaude
andauthored
feat: add terraform-lambda skill for agentic Lambda scaffolding (#191)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0c0ffba commit 0c7e8e2

6 files changed

Lines changed: 1243 additions & 1 deletion

File tree

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,13 @@ see [examples](examples/deployment) for details.
360360

361361
## Bootstrap new projects
362362

363-
In case you are using [go](https://golang.org/) for developing your Lambda functions, you can also use [func](https://github.com/moritzzimmer/func) to bootstrap your project and get started quickly.
363+
This module ships with an [Agent Skill](https://skills.sh/) to scaffold Lambda projects using AI coding agents:
364+
365+
```bash
366+
npx skills add moritzzimmer/terraform-aws-lambda
367+
```
368+
369+
Then use `/terraform-lambda` or just ask your agent to create a Lambda function — the skill triggers automatically on natural language prompts like *"scaffold a Python Lambda triggered by SQS"*.
364370

365371
## How do I contribute to this module?
366372

skills/terraform-lambda/SKILL.md

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
---
2+
name: terraform-lambda
3+
description: >
4+
Scaffold a complete AWS Lambda function project using the terraform-aws-lambda
5+
Terraform module (moritzzimmer/lambda/aws). Generates Terraform configuration,
6+
runtime-specific build tooling (Makefile), and minimal function code with
7+
correct event source wiring and IAM setup. Use this skill whenever the user
8+
wants to create a new Lambda function, scaffold a Lambda project, set up a
9+
serverless function with Terraform, or asks about using the terraform-aws-lambda
10+
module. Also trigger when the user mentions "new lambda", "lambda scaffold",
11+
"lambda boilerplate", or wants to add a Lambda function to their infrastructure.
12+
argument-hint: "[function-name] [runtime] [event-source]"
13+
---
14+
15+
# Create Lambda
16+
17+
Scaffold production-ready AWS Lambda functions using the
18+
[terraform-aws-lambda](https://registry.terraform.io/modules/moritzzimmer/lambda/aws)
19+
Terraform module.
20+
21+
## When to use this skill
22+
23+
- User wants to create a new Lambda function
24+
- User wants to scaffold a Lambda project with Terraform
25+
- User is setting up serverless infrastructure on AWS with Terraform
26+
- User asks how to use the `moritzzimmer/lambda/aws` module
27+
28+
## Gather requirements
29+
30+
Before generating anything, establish what the user needs. If they provided
31+
arguments via `/terraform-lambda`, parse them. Otherwise ask concisely:
32+
33+
1. **Function name** (required) — must be a valid Lambda function name
34+
2. **Runtime** — Go, Python, Java, .NET, Node.js, or container image (default: Python)
35+
3. **Event source** — what triggers the function: SQS, SNS, Kinesis, DynamoDB Streams, CloudWatch Events/schedule, API Gateway, S3, or none (default: none)
36+
4. **Extra features** — VPC, EFS, X-Ray tracing, Lambda Insights, SSM parameters, CloudWatch log retention, snap_start (Java only)
37+
5. **CI/CD pipeline** — does the user want CodeDeploy-based deployments? If so:
38+
- S3 or ECR source?
39+
- **S3 bucket**: Does an S3 bucket already exist (reference via `data` source
40+
or bucket name), or should the skill create a new one inline? This matters
41+
for the Terraform setup — ask explicitly.
42+
- Deployment strategy: canary, linear, or all-at-once?
43+
- Auto-rollback on failure?
44+
6. **Target directory** — where to create the project (default: `./<function-name>/`)
45+
46+
If the user gives a one-liner like "create a Python Lambda triggered by SQS called order-processor", extract all info from that — don't ask again for things already stated.
47+
48+
## Generate the project
49+
50+
### Directory layout
51+
52+
```
53+
<function-name>/
54+
├── Makefile # Build and deploy
55+
├── terraform/
56+
│ ├── main.tf # Module usage + event source resources
57+
│ ├── variables.tf # Environment-specific variables
58+
│ ├── outputs.tf # Useful outputs from the module
59+
│ ├── versions.tf # Terraform and provider version constraints
60+
│ └── provider.tf # AWS provider configuration
61+
└── <source code> # Layout is runtime-specific (see references/runtimes.md)
62+
```
63+
64+
Source code location varies by runtime — Go puts `main.go` and `go.mod` at the
65+
project root (idiomatic Go), while Python uses `src/app/`, Java uses `src/`,
66+
etc. Always consult [references/runtimes.md](references/runtimes.md) for the
67+
correct layout.
68+
69+
### Terraform files
70+
71+
**versions.tf** — pin providers:
72+
```hcl
73+
terraform {
74+
required_version = ">= 1.5.7"
75+
76+
required_providers {
77+
aws = {
78+
source = "hashicorp/aws"
79+
version = ">= 6.0"
80+
}
81+
}
82+
}
83+
```
84+
85+
**provider.tf**:
86+
```hcl
87+
provider "aws" {
88+
region = var.region
89+
}
90+
```
91+
92+
**variables.tf** — always include `region`, add others as needed:
93+
```hcl
94+
variable "region" {
95+
description = "AWS region for all resources"
96+
type = string
97+
default = "eu-central-1"
98+
}
99+
```
100+
101+
**outputs.tf** — expose the most useful module outputs:
102+
```hcl
103+
output "function_arn" {
104+
value = module.<function_name>.arn
105+
}
106+
107+
output "function_name" {
108+
value = module.<function_name>.function_name
109+
}
110+
111+
output "role_arn" {
112+
value = module.<function_name>.role_arn
113+
}
114+
115+
output "invoke_arn" {
116+
value = module.<function_name>.invoke_arn
117+
}
118+
```
119+
120+
**main.tf** — the module block plus any event source resources. Key rules:
121+
122+
- Module source: `"moritzzimmer/lambda/aws"`, version: `"~> 8.6"`
123+
- Alphabetical attribute ordering inside the module block
124+
- Guard `source_code_hash` with `fileexists()` so `terraform validate` works
125+
without build artifacts:
126+
```hcl
127+
source_code_hash = fileexists(local.artifact) ? filebase64sha256(local.artifact) : null
128+
```
129+
- Default architecture: `["arm64"]` (Graviton — better price/performance)
130+
- **Runtime versions**: Do not rely on your training data for Lambda runtime
131+
identifiers — they go stale quickly (e.g., `java21` is outdated, `java25`
132+
is current). Before generating code, look up the latest runtime by searching
133+
the web for "AWS Lambda supported runtimes" or checking
134+
`https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html`. If you
135+
cannot verify, leave a `# TODO: verify this is the latest runtime` comment
136+
next to the runtime value so the user knows to check.
137+
- Always include `tags = { managed_by = "terraform" }`
138+
139+
For runtime-specific settings, event source patterns, and the full variable
140+
reference, consult the reference files:
141+
142+
- [references/runtimes.md](references/runtimes.md) — runtime configs, Makefile
143+
templates, and sample function code for each supported language
144+
- [references/event-sources.md](references/event-sources.md) — how to wire up
145+
each event source type with correct Terraform resources
146+
- [references/variables.md](references/variables.md) — full variable reference
147+
grouped by feature area, with defaults and IAM implications
148+
- [references/deployment.md](references/deployment.md) — CodePipeline/CodeDeploy
149+
CI/CD pipeline setup using the deployment submodule
150+
151+
### CI/CD with the deployment submodule
152+
153+
When the user wants CodeDeploy-based deployments, the setup changes:
154+
155+
1. The main module uses **S3-based packaging** (`s3_bucket`/`s3_key`) instead of
156+
local `filename`, and sets `ignore_external_function_updates = true`
157+
2. An **`aws_s3_object` resource** uploads the initial build artifact to S3, and
158+
the main module references `s3_object_version` from it — this ensures the zip
159+
exists before the Lambda is created (without this, first apply fails with
160+
`NoSuchKey`). Use `lifecycle { ignore_changes = [etag] }` so CodePipeline
161+
deployments don't conflict.
162+
3. A **Lambda alias** is created with `lifecycle { ignore_changes = [function_version] }`
163+
— CodeDeploy manages version shifts, not Terraform
164+
4. The **deployment submodule** (`moritzzimmer/lambda/aws//modules/deployment`)
165+
creates a CodePipeline + CodeDeploy pipeline
166+
167+
For the full pattern with examples (canary, rollback, alarms), see
168+
[references/deployment.md](references/deployment.md).
169+
170+
## Important guardrails
171+
172+
These matter because getting them wrong causes hard-to-debug issues:
173+
174+
- **fileexists() guard**: Always wrap `filebase64sha256()` — without it,
175+
`terraform validate` and `terraform plan` fail when the zip hasn't been built
176+
yet (e.g., in CI before the build step).
177+
178+
- **arm64 default**: Use `architectures = ["arm64"]` unless the user has a
179+
reason for x86_64 (e.g., native dependencies that don't support ARM). Graviton
180+
is ~20% cheaper and generally faster for Lambda.
181+
182+
- **Memory defaults by runtime**: Go/Node.js: 128MB. Python/.NET: 256MB.
183+
Java: 512MB. These reflect typical cold-start and memory needs — the user can
184+
always override.
185+
186+
- **snap_start is Java-only**: Setting it for other runtimes silently does
187+
nothing useful. Only suggest it when runtime is Java.
188+
189+
- **S3 bucket: use `.bucket`, not `.id`**: When passing an S3 bucket to the
190+
deployment module, always use `aws_s3_bucket.x.bucket` (the name string),
191+
not `aws_s3_bucket.x.id`. The `.id` attribute can cause "Invalid count
192+
argument" errors because the deployment module uses
193+
`count = var.s3_bucket != ""` at plan time.
194+
195+
- **lambda_at_edge + VPC are incompatible**: Lambda@Edge runs at CloudFront
196+
edge locations and cannot access a VPC.
197+
198+
- **reserved_concurrent_executions = 0 disables the function**: If the user
199+
wants to limit concurrency, suggest a positive number. Zero means "cannot run."
200+
201+
- **IAM is automatic**: The module attaches the right IAM policies based on
202+
which features are enabled (VPC, tracing, SSM, event sources, logs). Don't
203+
create separate IAM resources for these — it's already handled.
204+
205+
## After generation
206+
207+
**Verify the generated code builds.** After writing all files, run `make package`
208+
(or the equivalent build command) to confirm the project compiles and packages
209+
successfully. If it fails, fix the issue before presenting the result to the
210+
user. Common causes: missing `using`/`import` statements, wrong package names
211+
in build files, or mismatched handler signatures.
212+
213+
Once the files are written:
214+
215+
1. Suggest `make help` to see available targets
216+
2. `make tf` runs `terraform init` + `terraform plan`
217+
3. `make tf MODE=apply` to deploy
218+
4. `make package` to just build the artifact without deploying
219+
220+
If the user wants to add features later (like adding an SQS trigger to an
221+
existing function), read the current Terraform config and modify it — don't
222+
regenerate from scratch.

0 commit comments

Comments
 (0)