autojanet/skills/opentofu-module/SKILL.md
Zoë cc74ad0bd0
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
fix: use library/ Harbor project, add skills, fix pipeline secrets
- .woodpecker.yaml: image paths -> library/autojanet-{agent,dispatcher}
- .woodpecker.yaml: secret names RS_HARBOR_USER / RS_HARBOR_PASS (global)
- container/Dockerfile: restore COPY skills/, skills/ populated from opencode config
- skills/: 84 opencode skills bundled into image
- k8s/manifests: update image refs to library/
2026-05-30 15:43:14 -07:00

4.6 KiB

name description
opentofu-module 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/<name>/
  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

terraform {
  required_version = ">= 1.6"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

variables.tf

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

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:

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)

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

# 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)