agentsclimarketplace

Infrastructure as code

Skill MARUCIE/openclaw-foundry/web/public/packs/spellbook-platform-engineer/skills/infrastructure-as-code

Use when writing Terraform for cloud resources, setting up remote state, structuring modules for reuse, managing multiple environments, reviewing a plan before apply, or importing and resolving state drift.From its SKILL.md

Install
npx -y skills add MARUCIE/openclaw-foundry --skill infrastructure-as-code

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

  • 1 stars1 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

13.7 KB, ~3.6k tokens by cl100k_base, as published. Nobody here has run it

是什么

这是一份 IaC(基础设施即代码)规范,覆盖 Terraform 模块结构、远端 state(状态文件)配置、多环境分离、plan 评审、状态漂移修复,让基础设施变更和业务代码一样走 PR 评审,告别手工点云控制台。

怎么用

  1. 新项目立项时,按本文档的目录骨架建好 modules、envs、shared 三层结构,避免后期重构成本。
  2. 远端 state 必须配 backend(远端存储)和锁机制,防止两个人同时 apply 把状态弄坏。
  3. 提 PR 时附上 terraform plan 输出,Review 时重点看 destroy 资源和 replace 资源两类高危改动。
  4. 状态漂移时按文档的 import 与 refresh 流程修复,不要直接 apply 覆盖,避免误删生产资源。
  5. 多环境用 workspace 或目录隔离,按规范配 variables,让 dev/staging/prod 走同一份代码不同参数。

架构图

flowchart LR
    A[Terraform 代码] --> B[PR 评审]
    B --> C[Plan 输出]
    C --> D[审批通过]
    D --> E[Apply 部署]
    E --> F[远端 State]

Infrastructure as Code

Terraform lets you define, provision, and version cloud infrastructure as declarative HCL code, enabling repeatable and reviewable infrastructure changes.

When to Activate

  • Writing Terraform for cloud resources (VPC, RDS, EKS, IAM, etc.)
  • Setting up a Terraform state backend
  • Creating a reusable Terraform module
  • Managing multiple environments (dev/staging/prod) with Terraform
  • Reviewing a terraform plan before applying
  • Dealing with state drift or importing existing resources

Core Building Blocks

# Provider — connects Terraform to a cloud API
terraform {
  required_version = ">= 1.7"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

# Variable — parameterise configuration
variable "aws_region" {
  type        = string
  description = "AWS region to deploy into"
  default     = "us-east-1"

  validation {
    condition     = can(regex("^[a-z]{2}-[a-z]+-[0-9]$", var.aws_region))
    error_message = "Must be a valid AWS region code."
  }
}

# Local — computed values used inside the module
locals {
  name_prefix = "${var.environment}-${var.app_name}"
  common_tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
    App         = var.app_name
  }
}

# Resource — a cloud resource
resource "aws_s3_bucket" "app_data" {
  bucket = "${local.name_prefix}-app-data"
  tags   = local.common_tags
}

# Data source — read existing resource without managing it
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

# Output — export values for other modules or humans
output "s3_bucket_arn" {
  value       = aws_s3_bucket.app_data.arn
  description = "ARN of the application data bucket"
}

State Management

Remote State Backend

State must be remote and locked — never store terraform.tfstate in git.

AWS (S3 + DynamoDB lock):

terraform {
  backend "s3" {
    bucket         = "my-org-terraform-state"
    key            = "services/payment-service/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"  # partition key: LockID (String)
  }
}

GCP (GCS):

terraform {
  backend "gcs" {
    bucket = "my-org-terraform-state"
    prefix = "services/payment-service"
  }
}

State Commands

terraform state list                      # list all managed resources
terraform state show aws_s3_bucket.data   # inspect a resource's state
terraform state mv OLD_ADDR NEW_ADDR      # rename without destroying
terraform state rm aws_s3_bucket.old      # remove from state (doesn't destroy)
terraform force-unlock LOCK_ID            # release a stuck lock

State file security: The state file contains sensitive values (RDS passwords, private keys). Ensure S3 bucket has:

  • Versioning enabled (recover from bad apply)
  • Server-side encryption
  • Block public access
  • Access restricted to CI role + team IAM role only

Module Structure

modules/
└── rds-postgres/
    ├── main.tf        # resources
    ├── variables.tf   # inputs
    ├── outputs.tf     # outputs
    └── README.md      # usage docs (required for shared modules)

Module Example

# modules/rds-postgres/variables.tf
variable "instance_class" {
  type    = string
  default = "db.t3.medium"
}
variable "db_name"      { type = string }
variable "subnet_ids"   { type = list(string) }
variable "vpc_id"       { type = string }
variable "tags"         { type = map(string); default = {} }

# modules/rds-postgres/outputs.tf
output "endpoint"   { value = aws_db_instance.this.endpoint }
output "db_name"    { value = aws_db_instance.this.db_name }
output "secret_arn" { value = aws_secretsmanager_secret.db_password.arn }

# Consuming the module
module "payment_db" {
  source = "../../modules/rds-postgres"

  db_name        = "payments"
  instance_class = "db.t3.large"
  subnet_ids     = module.vpc.private_subnet_ids
  vpc_id         = module.vpc.vpc_id
  tags           = local.common_tags
}

Module Versioning

# Pin to a Git tag (preferred for shared modules)
module "rds" {
  source = "git::https://github.com/my-org/tf-modules.git//rds-postgres?ref=v2.1.0"
}

# Terraform Registry
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"
}

Environment Strategy

Directory-per-Environment (recommended)

infra/
├── modules/
│   ├── vpc/
│   └── rds-postgres/
└── environments/
    ├── dev/
    │   ├── main.tf          # calls modules
    │   ├── terraform.tfvars # dev-specific values
    │   └── backend.tf       # dev state backend
    ├── staging/
    │   └── ...
    └── prod/
        └── ...

Pros: complete isolation, different providers per env, easy to cd into. Cons: some code duplication across environments.

Workspace (alternative)

terraform workspace new dev
terraform workspace select staging
terraform workspace list

Use terraform.workspace in HCL:

locals {
  instance_type = terraform.workspace == "prod" ? "db.r6g.xlarge" : "db.t3.medium"
}

Decision: Use directory-per-environment for significant infrastructure differences between envs. Use workspaces only for identical infrastructure with minor variable differences.

tfvars per Environment

# environments/prod/terraform.tfvars
aws_region     = "us-east-1"
environment    = "prod"
instance_class = "db.r6g.xlarge"
min_capacity   = 3
max_capacity   = 20

Plan/Apply Workflow

# 1. Init (first time, or after source changes)
terraform init

# 2. Format and validate
terraform fmt -recursive
terraform validate

# 3. Plan — save output for reproducible apply
terraform plan -out=tfplan -var-file=terraform.tfvars

# 4. Policy check (optional, using OPA/Conftest)
terraform show -json tfplan | conftest test -

# 5. Apply from the saved plan (no re-planning)
terraform apply tfplan

# 6. Verify
terraform state list

CI Pipeline Integration

# .github/workflows/terraform.yml
jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: "1.7.0"
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/terraform-plan
          aws-region: us-east-1
      - run: terraform init
      - run: terraform plan -out=tfplan
      - run: terraform show -json tfplan > tfplan.json
      - name: Policy check
        run: conftest test tfplan.json --policy policies/
      - uses: actions/upload-artifact@v4
        with:
          name: tfplan
          path: tfplan

  apply:
    needs: plan
    if: github.ref == 'refs/heads/main'
    environment: production   # requires approval in GitHub
    steps:
      - uses: actions/download-artifact@v4
        with: { name: tfplan }
      - run: terraform apply tfplan

Common Resource Patterns

VPC Networking (AWS)

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "${local.name_prefix}-vpc"
  cidr = "10.0.0.0/16"

  azs              = ["us-east-1a", "us-east-1b", "us-east-1c"]
  private_subnets  = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets   = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = var.environment != "prod"  # save cost in non-prod

  tags = local.common_tags
}

IAM Role + Policy (least privilege)

resource "aws_iam_role" "app_role" {
  name = "${local.name_prefix}-app"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "ec2.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
  tags = local.common_tags
}

resource "aws_iam_role_policy" "app_policy" {
  name = "app-policy"
  role = aws_iam_role.app_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["s3:GetObject", "s3:PutObject"]
        Resource = "${aws_s3_bucket.app_data.arn}/*"
      },
      {
        Effect   = "Allow"
        Action   = ["secretsmanager:GetSecretValue"]
        Resource = aws_secretsmanager_secret.db_password.arn
      }
    ]
  })
}

RDS Instance

resource "aws_db_instance" "postgres" {
  identifier        = "${local.name_prefix}-postgres"
  engine            = "postgres"
  engine_version    = "16.2"
  instance_class    = var.db_instance_class
  allocated_storage = 100
  storage_encrypted = true

  db_name  = var.db_name
  username = "app"
  password = random_password.db.result

  db_subnet_group_name   = aws_db_subnet_group.this.name
  vpc_security_group_ids = [aws_security_group.rds.id]

  backup_retention_period = var.environment == "prod" ? 30 : 7
  deletion_protection     = var.environment == "prod"
  skip_final_snapshot     = var.environment != "prod"

  tags = local.common_tags
}

Drift Detection and Import

Detect Drift

terraform plan   # shows resources that diverged from state

Any ~ (update) or -/+ (replace) on a resource you haven't changed = drift.

Import Existing Resources

# Import by resource address and cloud ID
terraform import aws_s3_bucket.legacy my-existing-bucket

# After import, write matching HCL or Terraform will show a diff

moved Block (safe refactoring)

# Rename a resource without destroying it
moved {
  from = aws_s3_bucket.data
  to   = aws_s3_bucket.app_data
}

See also: ci-cd, containerization, security

Red Flags

  • Storing terraform.tfstate in git — state files contain plaintext secrets (RDS passwords, private keys); use an S3+DynamoDB or GCS backend with server-side encryption from day one
  • Running terraform apply directly without a saved planterraform apply without -out=tfplan re-plans at apply time; what was reviewed in the PR and what actually runs can differ if state changed between plan and apply
  • Using terraform apply -auto-approve in CI on the production environment — auto-approve bypasses the human gate; production applies must require explicit approval via a GitHub environment protection rule
  • Module pinned to main or with no version constraintsource = "git::...?ref=main" means any upstream commit silently changes your infrastructure; pin to a specific git tag or Terraform registry version
  • IAM policy with "Action": "*" or "Resource": "*" — wildcard actions on all resources violates least privilege; scope to the exact actions and resource ARNs the role actually needs
  • terraform state rm used to "fix" a drift problem — removing a resource from state without destroying it creates orphaned cloud resources that accumulate cost and may introduce security gaps; use moved blocks or terraform import instead
  • Deleting a Terraform resource block to decommission a resource — removing the block from HCL causes terraform plan to show a destroy; validate intent with terraform plan and add lifecycle { prevent_destroy = true } on stateful resources
  • Sharing a single state file across all environments — one bad apply in staging can corrupt or lock the production state; each environment must have its own state file with its own backend key

Checklist

  • Remote state backend configured (S3+DynamoDB or GCS) — state never committed to git
  • State S3 bucket has versioning, encryption, and public access blocked
  • All resources tagged with environment, app, and managed_by = "terraform"
  • Infrastructure split into reusable modules with variables.tf and outputs.tf
  • Modules pinned to specific versions (git tag or registry version constraint)
  • terraform plan -out=tfplan used — apply from saved plan, not a re-plan
  • Policy checks (OPA/Conftest) run on plan JSON before apply
  • Production apply requires human approval (GitHub environment protection)
  • IAM roles follow least privilege — no "*" actions or resources in policy
  • Deletion protection enabled on RDS and other stateful prod resources
  • moved blocks used for resource renames — never destroy-and-recreate
  • terraform plan run after every manual change to detect drift

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,851. 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.