
Terraform
- 81 installs
- 31 repo stars
- Updated August 4, 2026
- iliaal/ai-skills
Provides Terraform and OpenTofu patterns for file organization, modules, native testing, state management, and security review.
About
Terraform covers HCL configuration, module structure, count-vs-for_each, native tftest testing, version pinning, and state and security practices. A developer uses it when writing or reviewing Terraform/OpenTofu IaC or migrating state.
- Module structure with examples/ and tftest native testing
- State security: remote backends, encryption, least-privilege rules
Terraform by the numbers
- 81 all-time installs (skills.sh)
- +7 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #587 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/iliaal/ai-skills --skill terraformAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 81 |
|---|---|
| repo stars | ★ 31 |
| Last updated | August 4, 2026 |
| Repository | iliaal/ai-skills ↗ |
What it does
Provides Terraform and OpenTofu patterns for file organization, modules, native testing, state management, and security review.
Files
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.hclKeep 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 runstate_key = "name"required forparallel = trueruns with independent state- 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+). Stacks (HCP, preview): orchestrates multiple configs as a single deployment unit -- evaluate for multi-environment patterns.
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 . moved { from = old; to = new }for refactoring resource names/modules without destroy-recreate. Remove block after apply.
Troubleshooting
- State lock stuck:
terraform force-unlock <ID>-- only after confirming no other operation running - Resource drift:
terraform plan -refresh-onlyto detect,terraform apply -refresh-onlyto accept - Replace tainted:
terraform apply -replace=ADDR(not deprecatedterraform taint) - Import existing:
importblocks (1.5+) for declarative import, orterraform import ADDR ID
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.
cidrsubnet(var.vpc_cidr, 8, count.index)for calculated subnet CIDRs -- never hardcode subnets- Multi-region:
provider "aws" { alias = "eu_west_1" }+providers = { aws = aws.eu_west_1 }in module blocks
Verify
Run before declaring done:
terraform fmt -check && terraform validate && tflint && trivy config .All commands must pass with zero errors.
ia-terraform Specification
Intent
ia-terraform is a language-class skill (stack-specific patterns and idioms). Terraform and OpenTofu configuration, modules, testing, state management, and HCL review. Use when working with Terraform, OpenTofu, HCL, tfvars, tftest, state migration, or IaC patterns.
Scope
In scope:
- Behaviors described in
SKILL.mdand routed via the should_trigger phrasings indistillery/tests/fixtures/triggers/ia-terraform.jsonl. - Updates to runtime behavior, structure, trigger precision, references, and validation.
Out of scope:
- Acting as the runtime instructions themselves (those live in
SKILL.md). - Trigger phrasings already covered by adjacent
ia-*skills (validate-pluginflags >70% description overlap as DUPLICATE_TRIGGER). - <!-- to fill in: domain-specific exclusions when the skill drifts -->
Trigger Context
- Class:
language - Hook regex:
plugins/whetstone/hooks/skill-patterns.sh->SKILL_PATTERNS[ia-terraform] - Common requests (from fixture should_trigger):
- "write a terraform module for the VPC and subnets"
- "review the infrastructure as code for the staging environment"
- "write a Terraform module for the VPC"
- Should not trigger for (from fixture should_not_trigger):
- "implement the shopping cart feature in React"
- "add PHPUnit tests for the order service"
- "write a Pulumi program for the same setup"
Source And Evidence Model
Authoritative sources:
SKILL.md-- runtime instructions and reference routing.references/*.md-- bundled supplementary content (0 file(s)).distillery/tests/fixtures/triggers/ia-terraform.jsonl-- positive and negative trigger phrasings under regression test.plugins/whetstone/hooks/skill-patterns.sh-- regex pattern that fires this skill.distillery/.eval-data/ia-terraform/-- harvested session examples (when present).
Data that must not be stored in this skill or its references:
- Secrets, credentials, tokens.
- Machine-specific filesystem paths (
/home/...,/Users/...,~/ai/...). The validator (MACHINE_PATH_LEAK) flags these as HIGH. - Private URLs, customer data, or unredacted personal information.
Coverage matrix
| Dimension | Status | Evidence |
|---|---|---|
| Trigger fixtures | complete | distillery/tests/fixtures/triggers/ia-terraform.jsonl (>=5 should_trigger, >=5 should_not_trigger) |
| Hook regex pattern | complete | plugins/whetstone/hooks/skill-patterns.sh (SKILL_PATTERNS[ia-terraform]) |
| Reference architecture | n/a | no references; SKILL.md is self-contained |
| Real-usage signal | <!-- populated by harvest-sessions when sessions exist --> | distillery/.eval-data/ia-terraform/ (created by harvest-sessions) |
Evaluation
Lightweight (run on every change):
python3 distillery/scripts/distiller.py validate-plugin --component ia-terraform
python3 distillery/scripts/distiller.py test-triggers --skill ia-terraformDeeper (when behavior risk warrants):
python3 distillery/scripts/distiller.py dspy-eval ia-terraform
python3 distillery/scripts/distiller.py diagnose-negatives ia-terraformAcceptance gates:
validate-plugin --component ia-terraformreturns 0 HIGH findings.test-triggers --skill ia-terraformreturns F1 = 1.0 with floors of 5 should_trigger and 5 should_not_trigger.- For dspy-eval, the composite score does not regress against the most recent saved baseline (see
distillery/.eval-data/ia-terraform/history.json).
Known Limitations
<!-- to fill in over time as drift surfaces. Default rule: any time diagnose-negatives surfaces a recurring failure pattern, document it here so future maintainers understand the trade-off the current implementation accepts. -->
Maintenance Notes
- Update
SKILL.mdwhen the runtime workflow, branch conditions, or output contract changes. - Update this
SPEC.mdwhen intent, scope, evidence model, evaluation gates, or maintenance expectations change. - Update the trigger fixture when adding new positive phrasings, removing stale ones, or expanding scope (the 5/5 floor is a hard validator gate).
- Update the hook regex in
skill-patterns.shwhenever fixture positives expose a missed phrasing; verify F1 = 1.0 witheval-triggersbefore committing. - Run the full release pipeline via
/release-- never bump versions or update CHANGELOG.md from a per-skill edit.