agentsclimarketplace

Infra iac terraform

Skill agents-inc/skills/dist/plugins/infra-iac-terraform/skills/infra-iac-terraform

Infrastructure as Code with HashiCorp TerraformFrom its SKILL.md

Install
npx -y skills add agents-inc/skills --skill infra-iac-terraform

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 20 stars20 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

17.2 KB, ~4.0k tokens by cl100k_base, as published. Nobody here has run it

Terraform Patterns

Quick Guide: Declarative infrastructure using HCL. Pin provider versions in required_providers and commit .terraform.lock.hcl. Use remote backends with state locking for team collaboration. Prefer for_each over count for non-identical resources. Use moved blocks for refactoring, import blocks for adopting existing infrastructure. Validate inputs with validation blocks and infrastructure with precondition/postcondition. Keep modules flat, composable, and single-purpose. Run terraform fmt and terraform validate before every commit.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md

(You MUST pin provider versions with constraints in required_providers and commit .terraform.lock.hcl to version control)

(You MUST use a remote backend with state locking for any shared or production infrastructure)

(You MUST use for_each with a map/set for non-identical resources -- count causes index-shift destruction on removal)

(You MUST never store secrets in .tf files, .tfvars, or state -- use environment variables (TF_VAR_*) or your secrets manager)

(You MUST run terraform plan and review the diff before every terraform apply -- never apply blindly)

</critical_requirements>


Detailed Resources:

  • examples/core.md - Resource definitions, variables, outputs, locals, data sources, provider configuration
  • examples/modules.md - Module structure, composition, versioning, registry publishing
  • examples/state.md - Remote backends, state locking, moved/import/removed blocks, workspaces
  • examples/patterns.md - for_each, dynamic blocks, lifecycle, conditions, validations
  • reference.md - Decision frameworks, CLI cheat sheet, file naming conventions

Auto-detection: Terraform, OpenTofu, HCL, .tf files, terraform init, terraform plan, terraform apply, terraform fmt, terraform validate, required_providers, terraform block, resource block, data source, module block, variable block, output block, locals, backend configuration, remote state, state locking, moved block, import block, for_each, count, dynamic block, lifecycle, precondition, postcondition, .terraform.lock.hcl, tfvars, provider configuration

When to use:

  • Writing or reviewing Terraform/OpenTofu configuration files (.tf)
  • Defining cloud resources, data sources, modules, variables, and outputs
  • Managing state backends, locking, and multi-environment deployments
  • Refactoring infrastructure with moved, import, and removed blocks
  • Structuring reusable modules for team or registry consumption

When NOT to use:

  • Application code deployment logic (that belongs in CI/CD pipelines)
  • Container orchestration configuration (Kubernetes manifests, Helm charts)
  • One-off scripting tasks better handled by shell scripts or CLI tools

Key patterns covered:

  • Provider pinning, lock files, and version constraints
  • Resource definitions with meta-arguments (for_each, count, depends_on, lifecycle)
  • Variable validation, locals for derived values, output descriptions
  • Remote backend configuration with state locking
  • Module structure (flat composition, single-purpose modules)
  • Refactoring with moved, import, and removed blocks
  • Custom conditions (precondition, postcondition, check blocks)
  • Dynamic blocks for repeated nested configuration
  • Environment management (directory-based vs workspaces)

<philosophy>

Philosophy

Terraform is a declarative infrastructure-as-code tool. You describe the desired end-state; Terraform determines the steps to reach it. The HCL configuration language is designed to be human-readable and machine-parseable.

Core principles:

  • Declarative, not imperative -- describe what you want, not how to get there
  • State is the source of truth -- Terraform tracks what it manages via state; protect it accordingly
  • Modules are the unit of reuse -- keep them flat, composable, and single-purpose
  • Pin everything -- provider versions, Terraform version, module versions; reproducibility is non-negotiable
  • Plan before apply -- always review the diff; never apply blindly in production

OpenTofu compatibility: OpenTofu is an open-source fork (MPL 2.0) that is syntax-compatible with Terraform 1.5.x. The patterns in this skill apply to both tools. OpenTofu uses .tofu file extensions for OpenTofu-only features and adds native state encryption.

</philosophy>
<patterns>

Core Patterns

Pattern 1: Provider and Version Pinning

Pin Terraform version and all provider versions. Commit .terraform.lock.hcl to version control.

# terraform.tf
terraform {
  required_version = ">= 1.9.0, < 2.0.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"  # Allows 5.x, blocks 6.0
    }
  }
}

Why this matters: Without version constraints, terraform init on different machines downloads different provider versions, causing inconsistent plans and mysterious drift. The lock file pins exact versions and cryptographic hashes.

Version constraint syntax: = 1.0.0 (exact), >= 1.0.0 (minimum), ~> 1.0 (allows 1.x, blocks 2.0), >= 1.0, < 2.0 (range).

See examples/core.md for full provider configuration with aliases and default tags.


Pattern 2: Resource Definitions and Meta-Arguments

Resources follow a standard argument ordering: meta-arguments first, resource arguments next, nested blocks after, lifecycle last.

resource "aws_instance" "web" {
  count = var.instance_count  # Meta-argument first

  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  tags = {
    Name = "web-${count.index}"
  }

  lifecycle {  # Lifecycle block last
    create_before_destroy = true
  }
}

Key meta-arguments: count, for_each, depends_on, provider, lifecycle. Place meta-arguments at the top, separated from resource arguments by a blank line.

See examples/core.md for argument ordering and naming conventions.


Pattern 3: for_each over count

Use for_each with a map or set for non-identical resources. count uses numeric indices -- removing an item from the middle shifts all subsequent indices, causing unnecessary destruction and recreation.

# for_each with a map -- stable keys, safe removal
resource "aws_iam_user" "team" {
  for_each = toset(var.team_members)  # ["alice", "bob", "carol"]

  name = each.value
}
# Removing "bob" only destroys bob's user -- alice and carol are untouched
# count -- index-based, dangerous on removal
resource "aws_iam_user" "team" {
  count = length(var.team_members)

  name = var.team_members[count.index]
}
# Removing "bob" (index 1) shifts carol from index 2 to 1 -- carol gets destroyed and recreated

When count is acceptable: Identical resources where the only difference is the count (e.g., count = var.enable_feature ? 1 : 0 for conditional creation).

See examples/patterns.md for for_each with maps, sets, and conditional patterns.


Pattern 4: Variables with Validation

Every variable needs type, description, and validation where constraints exist. Use named locals for derived values.

variable "environment" {
  type        = string
  description = "Deployment environment (dev, staging, production)"

  validation {
    condition     = contains(["dev", "staging", "production"], var.environment)
    error_message = "Environment must be dev, staging, or production."
  }
}

variable "instance_type" {
  type        = string
  description = "EC2 instance type for the application server"
  default     = "t3.micro"
}

Key rules: Always set type and description. Use validation blocks to catch invalid input at plan time. Never use default for secrets -- force the caller to provide them.

See examples/core.md for complex variable types, sensitive variables, and output definitions.


Pattern 5: Remote Backend with State Locking

Never use local state for shared infrastructure. Remote backends provide locking, versioning, and team collaboration.

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/network/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"  # State locking
  }
}

Critical: Backend configuration cannot use variables or locals -- values must be literal or passed via -backend-config flags during terraform init. Use partial configuration for dynamic values.

State file hierarchy: Organize state keys by environment and layer (e.g., prod/network/, prod/compute/, staging/network/) to minimize blast radius.

See examples/state.md for backend configuration, partial config, and state organization.


Pattern 6: Module Structure and Composition

Modules are the unit of reuse. Keep modules flat, single-purpose, and composable.

modules/
  vpc/
    main.tf          # Resources
    variables.tf     # Inputs
    outputs.tf       # Outputs
    README.md        # Usage docs (makes it public-facing)
  compute/
    main.tf
    variables.tf
    outputs.tf
# Root module calling child modules
module "vpc" {
  source = "./modules/vpc"

  cidr_block  = "10.0.0.0/16"
  environment = var.environment
}

module "compute" {
  source = "./modules/compute"

  subnet_ids  = module.vpc.private_subnet_ids
  environment = var.environment
}

Key principle: Keep the module tree flat. Deeply nested modules (module calling module calling module) are hard to debug and reuse. Prefer composition at the root level.

See examples/modules.md for module versioning, registry sources, and internal vs published modules.


Pattern 7: Refactoring with moved, import, and removed Blocks

Refactor infrastructure without destroying resources.

# Rename a resource -- state updated, infrastructure untouched
moved {
  from = aws_instance.web_server
  to   = aws_instance.app_server
}

# Adopt existing infrastructure into Terraform management
import {
  to = aws_s3_bucket.logs
  id = "my-existing-bucket-name"
}

# Remove from Terraform management without destroying the resource
removed {
  from = aws_instance.legacy

  lifecycle {
    destroy = false
  }
}

Always run terraform plan after adding these blocks to verify Terraform interprets the refactoring correctly. Look for "move" and "import" messages in the plan output.

See examples/state.md for module refactoring with moved blocks and bulk imports.


Pattern 8: Lifecycle Meta-Arguments

Control how Terraform manages resource lifecycle.

resource "aws_db_instance" "main" {
  # ... configuration ...

  lifecycle {
    prevent_destroy       = true   # Block accidental deletion
    create_before_destroy = true   # Zero-downtime replacement
    ignore_changes        = [tags] # External process manages tags
  }
}

When to use each:

  • prevent_destroy -- databases, DNS zones, state buckets (critical resources)
  • create_before_destroy -- load balancers, instances behind ASGs (zero-downtime)
  • ignore_changes -- auto-scaling managed attributes, externally tagged resources
  • replace_triggered_by -- force replacement when a dependency changes that Terraform does not detect

See examples/patterns.md for lifecycle combinations and replace_triggered_by.


Pattern 9: Custom Conditions (Preconditions, Postconditions, Checks)

Validate assumptions before provisioning and guarantees after.

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  lifecycle {
    precondition {
      condition     = data.aws_ami.ubuntu.architecture == "x86_64"
      error_message = "AMI must be x86_64 architecture."
    }

    postcondition {
      condition     = self.public_ip != ""
      error_message = "Instance must have a public IP assigned."
    }
  }
}

Precondition vs postcondition vs check:

  • precondition -- validates assumptions before creation (blocks plan)
  • postcondition -- validates guarantees after creation (blocks apply)
  • check block -- validates infrastructure state without blocking operations (warnings only)

See examples/patterns.md for check blocks and variable validation patterns.


Pattern 10: Dynamic Blocks

Generate repeated nested blocks from collections. Use sparingly -- overuse hurts readability.

resource "aws_security_group" "web" {
  name = "web-sg"

  dynamic "ingress" {
    for_each = var.ingress_rules

    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

When to use: Reusable modules where the number of nested blocks varies per caller. When not to use: Write nested blocks literally when the set is small and fixed. Dynamic blocks cannot generate meta-argument blocks (lifecycle, provisioner).

See examples/patterns.md for dynamic block patterns with conditionals.

</patterns>

<red_flags>

RED FLAGS

High Priority:

  • Missing .terraform.lock.hcl in version control -- different team members get different provider versions, causing plan drift and mysterious failures
  • Local state for shared infrastructure -- no locking means concurrent applies corrupt state; no remote backup means state loss is catastrophic
  • Secrets in .tf or .tfvars files -- committed to version control, visible in state file; use TF_VAR_* environment variables or your secrets manager
  • Using count for non-identical resources -- removing an item shifts indices, destroying and recreating unrelated resources
  • terraform apply without reviewing the plan -- auto-approve in production is how you delete databases
  • Unpinned provider versions -- version = ">= 5.0" without an upper bound allows major version upgrades that break everything

Medium Priority:

  • depends_on when an expression reference suffices -- depends_on causes overly conservative plans; let Terraform infer dependencies from expressions
  • Deeply nested module trees -- modules calling modules calling modules are hard to debug; keep the tree flat and compose at the root
  • ignore_changes = all -- Terraform will never update the resource again, even for intentional changes; be specific about which attributes to ignore
  • No description on variables and outputs -- undocumented inputs/outputs make modules unusable for anyone but the author
  • Hardcoded values instead of variables -- makes modules non-reusable; parameterize anything that changes between environments

Gotchas & Edge Cases:

  • Backend configuration cannot use variables, locals, or expressions -- values must be literal strings or passed via -backend-config during terraform init
  • prevent_destroy does not prevent destruction if you remove the resource block entirely -- it only prevents terraform destroy on the resource while the block exists
  • for_each keys must be known at plan time -- they cannot reference resource attributes that are computed during apply
  • moved blocks are processed once during terraform plan/apply -- remove them after the migration is applied to keep configuration clean
  • terraform state subcommands (mv, rm, pull, push) bypass safety checks -- use moved/removed blocks instead for auditable, reviewable refactoring
  • data sources are read during planning by default -- if they depend on resources being created in the same apply, use depends_on to defer the read
  • sensitive = true on variables prevents the value from appearing in plan output but does NOT encrypt it in state -- state encryption is a separate concern
  • terraform fmt only formats .tf files in the current directory -- use terraform fmt -recursive to format all subdirectories
  • toset() deduplicates -- if your list has duplicates, for_each = toset(var.list) silently drops them
  • .tfvars files are auto-loaded only if named terraform.tfvars or *.auto.tfvars -- other filenames require explicit -var-file flag

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md

(You MUST pin provider versions with constraints in required_providers and commit .terraform.lock.hcl to version control)

(You MUST use a remote backend with state locking for any shared or production infrastructure)

(You MUST use for_each with a map/set for non-identical resources -- count causes index-shift destruction on removal)

(You MUST never store secrets in .tf files, .tfvars, or state -- use environment variables (TF_VAR_*) or your secrets manager)

(You MUST run terraform plan and review the diff before every terraform apply -- never apply blindly)

Failure to follow these rules will cause state corruption, accidental resource destruction, secret exposure, and non-reproducible infrastructure.

</critical_reminders>

What ships with it: 5 files

41.6 KB alongside SKILL.md

examples/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.