--- name: opentofu-module description: Use when writing, editing, or reviewing OpenTofu/Terraform modules or configurations across AWS, Azure, OCI, or homelab environments. Triggers on tasks involving HCL, tofu commands, state management, IAM, EKS, IRSA, backends, or AFT. --- # OpenTofu Module Skill ## Overview Write and maintain OpenTofu modules for Zoe's infrastructure. Use `tofu` commands (not `terraform`). Providers span AWS (primary), Azure, and OCI free tier. ## Workflow 1. Define variables + outputs **before** writing resources — prevents refactoring 2. Write `versions.tf` first — sets provider constraints 3. Write resources in `main.tf` 4. `tofu fmt -recursive` and `tflint` before plan 5. `checkov -d .` — fix HIGH/CRITICAL before applying 6. `tofu plan -out=plan.out` → review → `tofu apply plan.out` 7. **Never `tofu apply` without a plan file in production** ## Standard Module Structure ``` modules// main.tf # resources variables.tf # input variables with descriptions and types outputs.tf # exported values versions.tf # required_providers with version constraints README.md # auto-generated by terraform-docs — do NOT write manually ``` ## Key Patterns ### versions.tf ```hcl terraform { required_version = ">= 1.6" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } ``` ### variables.tf ```hcl variable "cluster_name" { description = "Name of the EKS cluster" type = string } variable "tags" { description = "Tags to apply to all resources" type = map(string) default = {} } ``` ### S3 Backend ```hcl terraform { backend "s3" { bucket = "company-tofu-state" key = "env/production/cluster/terraform.tfstate" region = "us-west-2" dynamodb_table = "terraform-state-lock" encrypt = true } } ``` ### IRSA (IAM Roles for Service Accounts) — EKS Comes up constantly. Full pattern: ```hcl data "aws_eks_cluster" "cluster" { name = var.cluster_name } data "aws_iam_openid_connect_provider" "cluster" { url = data.aws_eks_cluster.cluster.identity[0].oidc[0].issuer } data "aws_iam_policy_document" "assume_role" { statement { effect = "Allow" actions = ["sts:AssumeRoleWithWebIdentity"] principals { type = "Federated" identifiers = [data.aws_iam_openid_connect_provider.cluster.arn] } condition { test = "StringEquals" variable = "${replace(data.aws_iam_openid_connect_provider.cluster.url, "https://", "")}:sub" values = ["system:serviceaccount:${var.namespace}:${var.service_account_name}"] } } } resource "aws_iam_role" "irsa" { name = "${var.cluster_name}-${var.name}-irsa" assume_role_policy = data.aws_iam_policy_document.assume_role.json tags = var.tags } ``` ### Cross-Account AssumeRole (OrganizationAccountAccessRole) ```hcl data "aws_iam_policy_document" "trust" { statement { effect = "Allow" actions = ["sts:AssumeRole"] principals { type = "AWS" # Use the role ARN pattern — NOT AWSReservedSSO_* (causes MalformedPolicyDocument) identifiers = ["arn:aws:iam::${var.management_account_id}:role/${var.deployer_role_name}"] } } } ``` ## Critical Gotcha: AWSReservedSSO_* Roles **NEVER use `AWSReservedSSO_*` ARNs as IAM principals in trust policies.** - Error: `MalformedPolicyDocument: Invalid principal in policy` - Fix: Use the underlying permission set role name pattern, or use `aws:PrincipalOrgID` condition instead ## State Operations ```bash # Push local state to remote after manual work tofu state push terraform.tfstate --force # Import existing resource tofu import aws_s3_bucket.example my-bucket-name # Move resource between state paths (refactoring) tofu state mv module.old.aws_instance.web module.new.aws_instance.web ``` ## Toolchain Quick Reference | Tool | Command | Purpose | |------|---------|---------| | Format | `tofu fmt -recursive` | Before every commit | | Lint | `tflint` | Catch provider-specific issues | | Security | `checkov -d .` | Fix HIGH/CRITICAL before apply | | Docs | `terraform-docs markdown . > README.md` | README generation only | | Plan | `tofu plan -out=plan.out` | Always use plan files in prod | ## Environment Notes - **AWS**: EKS, EBS, EFS, IAM, S3, Lambda, CodePipeline, GuardDuty, RDS Aurora, Organizations/AFT - **Azure**: AKS, Azure DevOps — state backend uses Azure Blob Storage - **OCI**: Free tier, budgets, some compute - **AFT**: Used for AWS org account provisioning via Terraform - **State backends**: S3+DynamoDB (AWS), Azure Blob (Azure)