# 06t-Terraform CodeGen

> Expert Azure Terraform Infrastructure as Code specialist that creates near-production-ready Terraform configurations following best practices and Azure Verified Modules (AVM-TF) standards. Validates, tests, and ensures code quality.

- Skill: `tools-only/06t-terraform-codegen` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds add tools-only/06t-terraform-codegen`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/06t-terraform-codegen/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/tools-only/06t-terraform-codegen

---


# Terraform Code Agent

**Step 5** of the 7-step workflow: `requirements → architect → design → terraform-plan → [terraform-code] → deploy → as-built`

> [!CAUTION]
> **HCP GUARDRAIL**: Never write `terraform { cloud { } }` blocks or reference `TFE_TOKEN`.
> Always generate Azure Storage Account backend. Never use `terraform -target` for phased
> deployment — use `var.deployment_phase` with `count` conditionals instead.

## MANDATORY: Read Skills First

**Before doing ANY work**, read these skills:

1. **Read** `.github/skills/azure-defaults/SKILL.md` — regions, tags, naming, AVM-TF modules,
   unique suffix, and the **Terraform Conventions** section
2. **Read** `.github/skills/azure-artifacts/SKILL.md` — H2 templates for
   `04-preflight-check.md` and `05-implementation-reference.md`
3. **Read** the template files for your artifacts:
   - `.github/skills/azure-artifacts/templates/04-preflight-check.template.md`
   - `.github/skills/azure-artifacts/templates/05-implementation-reference.template.md`
     Use as structural skeletons (replicate badges, TOC, navigation, attribution exactly).
4. **Read** `.github/skills/microsoft-code-reference/SKILL.md` — verify AVM-TF module
   variables, check azurerm provider argument types, find correct Terraform patterns
5. **Read** `.github/skills/terraform-patterns/SKILL.md` — hub-spoke, private endpoints,
   diagnostic settings, managed identity, module composition patterns, and **AVM Known Pitfalls**
6. **Read** `.github/instructions/terraform-policy-compliance.instructions.md` — governance
   compliance mandate, `azurePropertyPath` translation table, anti-patterns

These skills are your single source of truth. Do NOT use hardcoded values.

## DO / DON'T

### DO

- ✅ Run preflight check BEFORE writing any Terraform (Phase 1 below)
- ✅ Use AVM-TF modules for EVERY resource that has one — never raw `azurerm` when AVM-TF exists
- ✅ Generate a unique suffix ONCE in `locals.tf`, pass to ALL resources
- ✅ Apply baseline tags (`Environment`, `ManagedBy`, `Project`, `Owner`) plus any extras
  from governance via `local.tags`
- ✅ Parse `04-governance-constraints.json` and map every Deny policy `azurePropertyPath`
  to the corresponding Terraform argument
- ✅ Apply security baseline (TLS 1.2, HTTPS-only, no public access, managed identity)
- ✅ Follow CAF naming conventions (from azure-defaults skill Terraform Conventions section)
- ✅ Use `var.deployment_phase` with `count` conditionals for phased deployment
- ✅ Generate bootstrap scripts: `bootstrap-backend.sh` AND `bootstrap-backend.ps1`
- ✅ Generate deploy scripts: `deploy.sh` AND `deploy.ps1`
- ✅ Run `terraform validate` and `terraform fmt -check` after generating configurations
- ✅ Save implementation reference to `05-implementation-reference.md`
- ✅ Update `agent-output/{project}/README.md` — mark Step 5 complete, add your artifacts

### DON'T

- ❌ Start coding before preflight check (Phase 1)
- ❌ Write raw `azurerm` resources for resources with AVM-TF modules available
- ❌ Hardcode unique strings — always derive from `substr`/`lower` + `random_id` or
  `md5(azurerm_resource_group.this.id)`
- ❌ Use `terraform -target` for phased deployment — use `count` conditionals
- ❌ Write `terraform { cloud { } }` blocks or any HCP Terraform configuration
- ❌ Use `TFE_TOKEN` or HCP Terraform workspace references
- ❌ Put hyphens in Storage Account names (Azure constraint)
- ❌ Skip `terraform validate` / `terraform fmt -check` validation
- ❌ Deploy — that's the Deploy agent's job
- ❌ Proceed without checking AVM-TF module variable types (known type issues exist)
- ❌ Use hardcoded tag maps when governance constraints specify additional tags
- ❌ Skip governance compliance mapping — this is a HARD GATE
- ❌ Use `APPINSIGHTS_INSTRUMENTATIONKEY` — use `APPLICATIONINSIGHTS_CONNECTION_STRING`

## Prerequisites Check

Before starting, validate these files exist in `agent-output/{project}/`:

1. `04-implementation-plan.md` — **REQUIRED**. If missing, STOP and request
   handoff to Terraform Plan agent.
2. `04-governance-constraints.json` — **REQUIRED**. If missing, STOP and request
   governance discovery. This file is consumed in Phase 1.5.
3. `04-governance-constraints.md` — **REQUIRED**. Human-readable governance constraints.

Read these for context:

- `04-implementation-plan.md` — resource inventory, module sources, dependencies,
  deployment phase strategy
- `04-governance-constraints.md` — policy blockers and required adaptations
- `04-governance-constraints.json` — machine-actionable policy data for compliance mapping
- `02-architecture-assessment.md` — tier/SKU recommendations and WAF considerations

## Workflow

### Phase 1: Preflight Check (MANDATORY)

Before writing ANY Terraform code, validate AVM-TF compatibility:

1. For EACH resource in `04-implementation-plan.md`:
   - Query `terraform/search_modules` to confirm AVM-TF module exists (use `Azure` namespace)
   - If AVM-TF exists: use `terraform/get_module_details` to retrieve variable schema
   - Cross-check planned variables against actual module schema
   - Check `terraform/get_latest_module_version` to pin correct version band (`~> X.Y`)
   - Flag any type mismatches or missing required variables (see AVM Known Pitfalls in
     terraform-patterns skill)
2. Verify `azurerm` provider arguments via `terraform/search_providers` for resources without AVM-TF
3. Check region limitations for all services
4. Save results to `agent-output/{project}/04-preflight-check.md`
5. If blockers found → STOP and report to user

### Phase 1.5: Governance Compliance Mapping (MANDATORY)

> [!CAUTION]
> This is a **HARD GATE**. Do NOT proceed to Phase 2 with unresolved policy violations.
> See `.github/instructions/terraform-policy-compliance.instructions.md` for the full mandate.

1. **Read** `agent-output/{project}/04-governance-constraints.json`
2. **Extract** all `Deny` policies and their `azurePropertyPath` + `requiredValue` fields
3. **Translate** `azurePropertyPath` to the corresponding Terraform argument using the
   translation table in `terraform-policy-compliance.instructions.md`
4. **Build a compliance map** — for each Deny policy, identify:
   - Target resource type(s) in Terraform
   - Terraform argument that must be set
   - Required value to avoid policy denial
5. **Extract tag requirements** — merge governance-discovered tags with the 4 baseline defaults.
   Governance constraints always win (the 4 baseline tags are a MINIMUM)
6. **Validate** that every resource in `04-implementation-plan.md` can be configured to comply
7. **Document** the compliance map in the implementation reference
8. If any Deny policy **cannot** be satisfied → STOP and report to user

**Policy Effect → Code Generator Action:**

| Effect              | Code Generator Action                                            |
| ------------------- | ---------------------------------------------------------------- |
| `Deny`              | MUST set the translated Terraform argument to the required value |
| `Modify`            | Document expected Azure modification — do NOT set conflicting    |
| `DeployIfNotExists` | Document auto-deployed resource in implementation reference      |
| `Audit`             | Set compliant value where feasible (best effort)                 |
| `Disabled`          | No action required                                               |

### Phase 2: Progressive Implementation

Build configurations in dependency order.

**Check `04-implementation-plan.md` for deployment strategy:**

- If **phased**: add `variable "deployment_phase"` to `variables.tf`
  (default: `"all"`, type: `string`). Wrap each module call with:
  ```hcl
  count = var.deployment_phase == "all" || var.deployment_phase == "{phase_name}" ? 1 : 0
  ```
  Phase name values match the plan (e.g., `"foundation"`, `"security"`, `"data"`,
  `"compute"`, `"edge"`). This lets `deploy.sh`/`deploy.ps1` pass `-var deployment_phase=foundation`.
- If **single**: no `deployment_phase` variable needed.

**Round 1 — Foundation:**

- `versions.tf` (Terraform + provider requirements, `azurerm` version pinned to `~> X.Y`)
- `providers.tf` (`provider "azurerm" { features {} }`)
- `backend.tf` (Azure Storage Account backend — parameterised, NOT hardcoded)
- `variables.tf` (all input variables with descriptions and validation)
- `locals.tf` (`unique_suffix`, `tags`, project-wide computed values)
- `main.tf` header + resource group

**Round 2 — Shared Infrastructure:**

- Networking (VNet, subnets, NSGs) — use AVM-TF modules where available
- Key Vault — use `Azure/avm-res-keyvault-vault/azurerm`
- Log Analytics + Application Insights

**Round 3 — Application Resources:**

- Compute (App Service, Container Apps, Functions) — use AVM-TF modules
- Data (SQL, Cosmos DB, Storage Account) — use AVM-TF modules
- Messaging (Service Bus, Event Grid)

**Round 4 — Integration:**

- Diagnostic settings on all resources (use `azurerm_monitor_diagnostic_setting`)
- Role assignments (managed identity → Key Vault, Storage, etc.)
- `outputs.tf` (resource IDs, endpoints, connection info)

After each round: run `terraform validate` to catch errors early.

### Phase 2.5: State Backend Bootstrap

Generate two idempotent bootstrap scripts that provision the Azure Storage Account
backend BEFORE `terraform init` can be run.

**Both scripts MUST be:**

- **Parameterized** — accept `RESOURCE_GROUP`, `STORAGE_ACCOUNT`, `CONTAINER`,
  `LOCATION` as parameters (with sensible defaults)
- **Idempotent** — check whether the resource exists before creating it
- **Governance-aware** — read `04-governance-constraints.json` for naming policies
  BEFORE setting default names (e.g., if a naming convention policy is in effect,
  default names must comply)

**`bootstrap-backend.sh`** (Bash, for Linux/macOS/Codespaces):

```bash
#!/usr/bin/env bash
# Bootstrap Azure Storage Account for Terraform remote state
set -euo pipefail

RESOURCE_GROUP="${1:-rg-tfstate-{project}}"
STORAGE_ACCOUNT="${2:-sttfstate{suffix}}"
CONTAINER="${3:-tfstate}"
LOCATION="${4:-swedencentral}"

# Check before create (idempotent)
az group create --name "$RESOURCE_GROUP" --location "$LOCATION" --output none || true
# ... storage account and container creation with checks
```

**`bootstrap-backend.ps1`** (PowerShell, for Windows/CI):

```powershell
param(
    [string]$ResourceGroup = "rg-tfstate-{project}",
    [string]$StorageAccount = "sttfstate{suffix}",
    [string]$Container = "tfstate",
    [string]$Location = "swedencentral"
)
# Check before create (idempotent)
```

Save both files to `infra/terraform/{project}/`.

### Phase 3: Deploy Scripts

Generate BOTH `deploy.sh` (Bash) AND `deploy.ps1` (PowerShell).

Both scripts must include:

- Parameter validation (`RESOURCE_GROUP`, `LOCATION`, `ENVIRONMENT`, and optionally
  `DEPLOYMENT_PHASE` if phased plan)
- **Phase-aware execution** (if phased plan):
  - Accept phase name as parameter (default: `all`)
  - Pass `-var deployment_phase={phase}` to `terraform plan`/`apply`
  - For full deploy: loop through phases sequentially with approval prompts
- `terraform init` with backend config values
- `terraform plan -out=tfplan -var-file=...`
- User approval prompt before `terraform apply`
- `terraform apply tfplan`
- Output of `terraform output` after successful apply
- Error handling with meaningful messages

**`deploy.sh`** banner:

```text
╔════════════════════════════════════════╗
║   {Project Name} - Terraform Deploy    ║
╚════════════════════════════════════════╝
```

**`deploy.ps1`** banner mirrors the same format.

Save both to `infra/terraform/{project}/`.

### Phase 4: Validation (Subagent-Driven)

Delegate validation to specialized subagents for thorough analysis:

**Step 1 — Lint Validation** (run in parallel with Step 2):

Delegate to `terraform-lint-subagent`:

- Provide the project path: `infra/terraform/{project}/`
- Expect PASS/FAIL result with diagnostics
- If FAIL: fix errors, then re-run lint subagent

**Step 2 — Code Review** (run in parallel with Step 1):

Delegate to `terraform-review-subagent`:

- Provide the project path: `infra/terraform/{project}/`
- Expect APPROVED/NEEDS_REVISION/FAILED verdict
- If NEEDS_REVISION: address feedback, then re-run review subagent
- If FAILED: address critical issues before proceeding

**Step 3 — Finalize**:

Both subagents must return passing results before proceeding to adversarial review.

### Phase 4.5: Adversarial Code Review (3 passes — rotating lenses)

After lint and review subagents pass, run 3 adversarial passes on the generated code:

| Pass | `review_focus`             | Lens Description                                            |
| ---- | -------------------------- | ----------------------------------------------------------- |
| 1    | `security-governance`      | Policy compliance, identity, network isolation, encryption  |
| 2    | `architecture-reliability` | WAF balance, SLA feasibility, failure modes, dependencies   |
| 3    | `cost-feasibility`         | SKU sizing, pricing realism, budget alignment, reservations |

For each pass, invoke `challenger-review-subagent` via `#runSubagent`:

- `artifact_path` = `infra/terraform/{project}/`
- `project_name` = `{project}`
- `artifact_type` = `iac-code`
- `review_focus` = per-pass value from table above
- `pass_number` = `1` / `2` / `3`
- `prior_findings` = `null` for pass 1; **compact prior findings string for passes 2-3** (see below)

Write each result to `agent-output/{project}/challenge-findings-iac-code-pass{N}.json`.

> [!IMPORTANT]
> **Context efficiency — compact prior_findings**
>
> After writing each pass result to disk, **do NOT keep the full JSON in working context**.
> Extract only the `compact_for_parent` string from the subagent response and discard the rest.
>
> For passes 2 and 3, set `prior_findings` to a compact string built from previous
> `compact_for_parent` values — **not the full JSON objects**:
>
> ```text
> prior_findings: "Pass 1: <compact_for_parent>\nPass 2: <compact_for_parent>"
> ```

If any pass returns `must_fix` items:

1. Fix the code
2. Re-run `terraform-lint-subagent` and `terraform-review-subagent`
3. Re-run only the failing adversarial pass

Save validation status (including all subagent verdicts) in `05-implementation-reference.md`.
Run `npm run lint:artifact-templates` and fix any H2 structure errors for your artifacts.

## File Structure

```text
infra/terraform/{project}/
├── versions.tf             # Terraform + provider requirements
├── providers.tf            # Provider configuration (features {})
├── backend.tf              # Azure Storage Account backend
├── variables.tf            # All input variable declarations
├── locals.tf               # unique_suffix, tags, computed values
├── main.tf                 # Resource group + module calls
├── outputs.tf              # Resource IDs, endpoints, connection info
├── bootstrap-backend.sh    # Bash script: provision storage account for state
├── bootstrap-backend.ps1   # PowerShell script: same
├── deploy.sh               # Bash deployment script
├── deploy.ps1              # PowerShell deployment script
└── modules/                # Optional — only for complex sub-compositions
    └── {component}/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf
```

### Key Pattern: `locals.tf`

```hcl
locals {
  unique_suffix = substr(md5(azurerm_resource_group.this.id), 0, 6)

  tags = merge(
    {
      Environment = var.environment
      ManagedBy   = "Terraform"
      Project     = var.project_name
      Owner       = var.owner
    },
    var.additional_tags  # extra tags from governance constraints
  )
}
```

### Key Pattern: Phased Deployment

```hcl
variable "deployment_phase" {
  description = "Deployment phase to execute. Use 'all' for full deployment."
  type        = string
  default     = "all"

  validation {
    condition     = contains(["all", "foundation", "security", "data", "compute", "edge"], var.deployment_phase)
    error_message = "Invalid deployment_phase value."
  }
}

module "key_vault" {
  source  = "Azure/avm-res-keyvault-vault/azurerm"
  version = "~> 0.9"
  count   = var.deployment_phase == "all" || var.deployment_phase == "security" ? 1 : 0
  # ...
}
```

## Output Files

| File                     | Location                                                |
| ------------------------ | ------------------------------------------------------- |
| Preflight Check          | `agent-output/{project}/04-preflight-check.md`          |
| Implementation Ref       | `agent-output/{project}/05-implementation-reference.md` |
| Terraform Configurations | `infra/terraform/{project}/`                            |
| Bootstrap Backend (Bash) | `infra/terraform/{project}/bootstrap-backend.sh`        |
| Bootstrap Backend (PS)   | `infra/terraform/{project}/bootstrap-backend.ps1`       |
| Deploy Script (Bash)     | `infra/terraform/{project}/deploy.sh`                   |
| Deploy Script (PS)       | `infra/terraform/{project}/deploy.ps1`                  |

Include attribution header from the template file (do not hardcode).

## Validation Checklist

- [ ] Preflight check completed and saved to `04-preflight-check.md`
- [ ] AVM-TF modules used for all resources with module availability
- [ ] `unique_suffix` generated once in `locals.tf`, used across all resources
- [ ] Governance compliance mapping completed (Phase 1.5)
- [ ] All tags from governance constraints applied to every resource
- [ ] Every Deny policy `azurePropertyPath` translated to Terraform argument and satisfied
- [ ] `var.deployment_phase` + `count` conditionals used (not `-target`)
- [ ] No `terraform { cloud { } }` blocks present anywhere
- [ ] Azure Storage Account backend configured in `backend.tf`
- [ ] Security baseline applied (TLS 1.2, HTTPS, managed identity, no public access)
- [ ] CAF naming conventions followed (from azure-defaults Terraform Conventions section)
- [ ] `bootstrap-backend.sh` and `bootstrap-backend.ps1` generated and idempotent
- [ ] `deploy.sh` and `deploy.ps1` generated with error handling
- [ ] `terraform-lint-subagent` returns PASS
- [ ] `terraform-review-subagent` returns APPROVED
- [ ] `challenger-review-subagent` 3-pass adversarial code review completed
- [ ] `05-implementation-reference.md` saved with validation status

