terraform
Terraform and OpenTofu configuration, modules, testing, state management, and HCL review. Use for "terraform module", "terraform test", "infrastructure as code", "IaC", "HCL", "tfvars", "terraform plan", "terraform apply", "OpenTofu", "tftest", or multi-environment patterns.
Install
npx skills add https://github.com/iliaal/whetstone/tree/master/distillery/generated-skills/terraform
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install iliaal-whetstone@llmmart
git clone https://github.com/iliaal/whetstone.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole iliaal/whetstone collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Terraform & OpenTofu
File Organization & Naming
| File | Purpose |
|---|---|
terraform.tf |
Terraform + provider version requirements |
providers.tf |
Provider configurations |
main.tf |
Primary resources and data sources |
variables.tf |
Input variables (alphabetical) |
outputs.tf |
Output values (alphabetical) |
locals.tf |
Local values |
- Lowercase with underscores:
web_api, notwebAPIorweb-api - Descriptive nouns excluding resource type:
aws_instance.web_apinotaws_instance.web_api_instance - Singular, not plural
thisfor singleton resources (one of that type per module)- Contextual variable prefixes:
vpc_cidr_blocknotcidr
Block Ordering
Resources: count/for_each (blank line after) → arguments → nested blocks → tags → depends_on → lifecycle (last)
Variables: description → type → default → validation → nullable
Every variable needs type + description. Every output needs description. Mark secrets sensitive = true.
Module Structure
| Type | Scope | Example |
|---|---|---|
| Resource Module | Single logical group | VPC + subnets, SG + rules |
| Infrastructure Module | Collection of resource modules | Networking + compute for one region |
| Composition | Complete infrastructure | Spans regions/accounts |
module-name/
├── main.tf, variables.tf, outputs.tf, versions.tf
├── examples/
│ ├── minimal/
│ └── complete/
└── tests/
└── defaults.tftest.hcl
Keep modules small (single responsibility). examples/ double as documentation and integration test fixtures. Semantic versioning for all published modules.
count vs for_each
| Scenario | Use |
|---|---|
| Boolean toggle (create or skip) | count = condition ? 1 : 0 |
| Named/keyed items that may reorder | for_each = toset(list) or map |
| Fixed identical replicas | count = N |
Default to for_each — removing a middle item from a count list recreates all subsequent resources. Use count only for boolean conditionals or truly identical replicas.
Testing
| Situation | Approach |
|---|---|
| Quick validation | terraform fmt -check && terraform validate |
| Pre-commit | + tflint + trivy config . / checkov -d . |
| Logic validation (1.6+) | Native terraform test with command = plan |
| Cost-free unit tests (1.7+) | Native tests + mock_provider |
| Real infra validation | Native tests with command = apply, or Terratest (Go) |
Native test essentials (.tftest.hcl in tests/):
command = planfor fast unit tests;command = applyfor integration (default)assert { condition = expr; error_message = "..." }— multiple per run blockexpect_failures = [var.name]for negative testing (validate rejection of bad input)mock_provider "aws" { mock_resource "..." { defaults = { ... } } }— plan-mode only, no credentials, fast CIvariables {}at file level (all runs) or within arunblock (override)- Reference prior run outputs:
run.setup.vpc_id parallel = trueon independent runs with separate state — creates sync point at next sequential run- File naming:
*_unit_test.tftest.hcl(plan mode) vs*_integration_test.tftest.hcl(apply mode)
Version Pinning
| Component | Strategy | Example |
|---|---|---|
| Terraform | Pin minor | required_version = "~> 1.9" |
| Providers | Pin major | version = "~> 5.0" |
| Modules (prod) | Pin exact | version = "5.1.2" |
| Modules (dev) | Allow patch | version = "~> 5.1" |
Key modern features: moved blocks (1.1+), optional() with defaults (1.3+), native testing (1.6+), mock providers (1.7+), cross-variable validation (1.9+), write-only arguments (1.11+).
State & Security
- Remote backend with locking: S3+DynamoDB, Azure Blob, GCS, or Terraform Cloud. Never local state for shared infrastructure.
- Encrypt state at rest. Never commit
.tfstate,.terraform/, or*.tfplan. Always commit.terraform.lock.hcl. default_tagson provider for consistent resource tagging.- Encryption at rest on all storage. Private networking by default — public access is opt-in.
- Least-privilege security groups. No
0.0.0.0/0ingress without explicit justification. - Never hardcode credentials — use assume_role, OIDC, or secrets managers.
- Pre-commit:
terraform fmt -recursive && terraform validate && trivy config .
Dependency Management
Use locals with try() to control deletion ordering without explicit depends_on:
locals {
vpc_id = try(aws_vpc_ipv4_cidr_block_association.this[0].vpc_id, aws_vpc.this.id, "")
}
This forces Terraform to destroy subnets before CIDR associations — prevents deletion errors.
Files (whetstone)
-
manifest.json 1 KB
{ "query": "terraform", "search_queries": [ "terraform" ], "generated": "2026-02-13", "token_count": 1376, "sources": [ { "id": "wshobson/agents/terraform-module-library", "installs": 1556, "sha1": "5b8d7dcb6505cde2788f5635110e09684eaca99a" }, { "id": "hashicorp/agent-skills/terraform-style-guide", "installs": 552, "sha1": "e132c6da8852dc3eb0bcab62908e8db19014a70c" }, { "id": "hashicorp/agent-skills/terraform-test", "installs": 342, "sha1": "6ddadda37b788e4a0e7210d981e64d7e2668f136" }, { "id": "hashicorp/agent-skills/terraform-stacks", "installs": 252, "sha1": "37fa0d540f050d3d52115e3967c135ee9e656d18" }, { "id": "antonbabenko/terraform-skill/terraform-skill", "installs": 243, "sha1": "418a692d2368a69981acde5c361ece9c37dc35e7" }, { "id": "jeffallan/claude-skills/terraform-engineer", "installs": 221, "sha1": "cfcc21c735b8bb60f6920c4e462795c3028e45ba" } ] } -
SKILL.md 5 KB
--- name: terraform description: >- Terraform and OpenTofu configuration, modules, testing, state management, and HCL review. Use for "terraform module", "terraform test", "infrastructure as code", "IaC", "HCL", "tfvars", "terraform plan", "terraform apply", "OpenTofu", "tftest", or multi-environment patterns. --- # Terraform & OpenTofu ## File Organization & Naming | File | Purpose | |------|---------| | `terraform.tf` | Terraform + provider version requirements | | `providers.tf` | Provider configurations | | `main.tf` | Primary resources and data sources | | `variables.tf` | Input variables (alphabetical) | | `outputs.tf` | Output values (alphabetical) | | `locals.tf` | Local values | - Lowercase with underscores: `web_api`, not `webAPI` or `web-api` - Descriptive nouns excluding resource type: `aws_instance.web_api` not `aws_instance.web_api_instance` - Singular, not plural - `this` for singleton resources (one of that type per module) - Contextual variable prefixes: `vpc_cidr_block` not `cidr` ## Block Ordering **Resources:** `count`/`for_each` (blank line after) → arguments → nested blocks → `tags` → `depends_on` → `lifecycle` (last) **Variables:** `description` → `type` → `default` → `validation` → `nullable` Every variable needs `type` + `description`. Every output needs `description`. Mark secrets `sensitive = true`. ## Module Structure | Type | Scope | Example | |------|-------|---------| | Resource Module | Single logical group | VPC + subnets, SG + rules | | Infrastructure Module | Collection of resource modules | Networking + compute for one region | | Composition | Complete infrastructure | Spans regions/accounts | ``` module-name/ ├── main.tf, variables.tf, outputs.tf, versions.tf ├── examples/ │ ├── minimal/ │ └── complete/ └── tests/ └── defaults.tftest.hcl ``` Keep modules small (single responsibility). `examples/` double as documentation and integration test fixtures. Semantic versioning for all published modules. ## count vs for_each | Scenario | Use | |----------|-----| | Boolean toggle (create or skip) | `count = condition ? 1 : 0` | | Named/keyed items that may reorder | `for_each = toset(list)` or `map` | | Fixed identical replicas | `count = N` | Default to `for_each` — removing a middle item from a `count` list recreates all subsequent resources. Use `count` only for boolean conditionals or truly identical replicas. ## Testing | Situation | Approach | |-----------|----------| | Quick validation | `terraform fmt -check && terraform validate` | | Pre-commit | + `tflint` + `trivy config .` / `checkov -d .` | | Logic validation (1.6+) | Native `terraform test` with `command = plan` | | Cost-free unit tests (1.7+) | Native tests + `mock_provider` | | Real infra validation | Native tests with `command = apply`, or Terratest (Go) | **Native test essentials** (`.tftest.hcl` in `tests/`): - `command = plan` for fast unit tests; `command = apply` for integration (default) - `assert { condition = expr; error_message = "..." }` — multiple per run block - `expect_failures = [var.name]` for negative testing (validate rejection of bad input) - `mock_provider "aws" { mock_resource "..." { defaults = { ... } } }` — plan-mode only, no credentials, fast CI - `variables {}` at file level (all runs) or within a `run` block (override) - Reference prior run outputs: `run.setup.vpc_id` - `parallel = true` on independent runs with separate state — creates sync point at next sequential run - File naming: `*_unit_test.tftest.hcl` (plan mode) vs `*_integration_test.tftest.hcl` (apply mode) ## Version Pinning | Component | Strategy | Example | |-----------|----------|---------| | Terraform | Pin minor | `required_version = "~> 1.9"` | | Providers | Pin major | `version = "~> 5.0"` | | Modules (prod) | Pin exact | `version = "5.1.2"` | | Modules (dev) | Allow patch | `version = "~> 5.1"` | Key modern features: `moved` blocks (1.1+), `optional()` with defaults (1.3+), native testing (1.6+), mock providers (1.7+), cross-variable validation (1.9+), write-only arguments (1.11+). ## State & Security - Remote backend with locking: S3+DynamoDB, Azure Blob, GCS, or Terraform Cloud. Never local state for shared infrastructure. - Encrypt state at rest. Never commit `.tfstate`, `.terraform/`, or `*.tfplan`. Always commit `.terraform.lock.hcl`. - `default_tags` on provider for consistent resource tagging. - Encryption at rest on all storage. Private networking by default — public access is opt-in. - Least-privilege security groups. No `0.0.0.0/0` ingress without explicit justification. - Never hardcode credentials — use assume_role, OIDC, or secrets managers. - Pre-commit: `terraform fmt -recursive && terraform validate && trivy config .` ## Dependency Management Use `locals` with `try()` to control deletion ordering without explicit `depends_on`: ```hcl locals { vpc_id = try(aws_vpc_ipv4_cidr_block_association.this[0].vpc_id, aws_vpc.this.id, "") } ``` This forces Terraform to destroy subnets before CIDR associations — prevents deletion errors.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.