--- name: apex-terraform-patterns user-invocable: false disable-model-invocation: false description: '**UTILITY SKILL** — Reusable Azure Terraform patterns: hub-spoke, private endpoints, diagnostics, AVM-TF modules. WHEN: "hub-spoke Terraform", "private endpoint module", "AVM-TF composition", "diagnostic settings", "plan interpretation". DO NOT USE FOR: Bicep code (apex-azure-bicep-patterns), ADRs (apex-azure-adr), diagrams (apex-python-diagrams).' compatibility: Requires Terraform >= 1.11, azurerm ~> 4.0, Azure CLI --- # Azure Terraform Patterns Skill Composable architecture building blocks for Azure Terraform. Complements `iac-terraform-best-practices.instructions.md` (style) and `apex-azure-defaults` skill (naming, tags, regions). > **Canonical sources** — the security baseline, AVM-first mandate, naming > conventions, required tags, and unique-suffix rule live in > [`apex-azure-defaults/SKILL.md`](../apex-azure-defaults/SKILL.md) and > [`iac-policy-compliance.md`](../../instructions/references/iac-policy-compliance.md). > This skill restates the rules tersely below for IaC-output convenience > only; in conflict, the canonical sources win. --- ## Quick Reference | Pattern | When to Use | Reference | | ------------------------ | ------------------------------------------------ | ------------------------------------------ | | Hub-Spoke Networking | Multi-workload environments with shared services | `references/hub-spoke-pattern.md` | | Private Endpoint Wiring | Any PaaS service requiring private connectivity | `references/private-endpoint-pattern.md` | | Diagnostic Settings | Every deployed resource (mandatory) | `references/common-patterns.md` | | Conditional Deployment | Optional resources controlled by variables | `references/common-patterns.md` | | Module Composition | Calling multiple AVM modules in root module | See inline example below | | Managed Identity | Any service-to-service authentication | `references/common-patterns.md` | | Budget & Cost Monitoring | Every deployment (mandatory) | `references/budget-pattern.md` | | Plan Interpretation | Pre-deployment validation and change analysis | `references/plan-interpretation.md` | | AVM Pitfalls | Set-type diffs, provider pins, 4.x changes | `references/avm-pitfalls.md` | | Provider Compatibility | Why azurerm 4.x / Terraform 1.11 floors | `references/avm-provider-compatibility.md` | | AVM Authoring | AVM certification requirements, compliance | `references/avm-authoring-requirements.md` | | Module Refactoring | Monolith → module extraction, state migration | `references/refactor-module.md` | --- ## Canonical Example — Module Composition Wire AVM child modules by passing outputs as inputs (`module..`); never hardcode IDs. **AVM-TF module versions in APEX-generated code MUST be exact semver (`version = "X.Y.Z"`)** — pinned at plan time from `registry.terraform.io` (newest stable in `modules[0].versions[]`). Range constraints (`~> X.Y`, `>= X.Y.Z`) are NOT allowed in `04-iac-contract.json` and will be flagged by `npm run validate:avm-versions`. Full code sample (resource group + key vault) and rationale in [`references/module-composition.md`](references/module-composition.md). --- ## Rules - **AVM-first**: Use `Azure/avm-res-*` registry modules over raw `azurerm_*` resources - **AVM-TF version pins**: Use exact semver resolved from the Registry API at plan time; follow the `apex-azure-defaults` exception policy - **Hub-spoke**: Spokes peer to hub only; never spoke-to-spoke - **Private endpoints**: Apply the canonical private-networking baseline and resolve DNS component ownership per [the private-endpoint pattern](references/private-endpoint-pattern.md); do not duplicate central or DINE resources. - **Diagnostics**: Every resource MUST have a diagnostic setting → Log Analytics - **Conditional**: Use `for_each` (keyed) over `count` (indexed) for named resources - **Identity**: SystemAssigned managed identity + RBAC; avoid keys/connection strings - **Provider pin**: `~> 4.0` (allows 4.x patches, blocks 5.0 until AVM modules accept it — see `references/avm-provider-compatibility.md`) - **Telemetry**: Set `enable_telemetry = false` in restricted-network environments - **Moved blocks**: Use `moved {}` when renaming resources to prevent destroy/recreate - **Budget**: Follow the canonical cost-monitoring contract; amount and notification inputs MUST be variables ## Steps Applying a Terraform pattern in a root module: 1. **Identify the pattern** — match your need to a row in [Quick Reference](#quick-reference) (hub-spoke, private endpoint, diagnostics, conditional, identity, budget, plan interpretation) 2. **Load the reference** — read the linked `references/*.md`; do not load all at once 3. **Compose AVM modules** — wire outputs as inputs (see [Canonical Example](#canonical-example--module-composition)); never hardcode IDs 4. **Pin the provider** — `~> 4.0` only; do not use `>= 3.0` or exact `= 4.x.y` 5. **Add diagnostics + budget** — every resource gets diagnostics; every deployment implements the governed cost-monitoring contract 6. **Plan before apply** — `terraform plan -out=plan.tfplan`; review for `~`/`-`/`+/-` operations against [`references/plan-interpretation.md`](references/plan-interpretation.md) 7. **Validate** — `terraform fmt -check`, `terraform validate`, `npm run validate:terraform`, `npm run validate:iac-security-baseline` ## Gotchas - **Set-type phantom diffs** — `azurerm_application_gateway`, `azurerm_lb`, `azurerm_network_security_group`, `azurerm_firewall`, `azurerm_frontdoor`: adding ONE element causes ALL elements to show `~` changes. Mitigation: inspect the plan's actual changes. Use `ignore_changes` only for blocks managed externally; do not suppress Terraform-owned changes just to hide noisy diffs. - **Provider pin `~> 4.0` is critical** — `>= 3.0` crosses breaking versions; `= 4.1.0` blocks patches. MUST use `~> 4.0`. - **`for_each` over `count` for named resources** — `count` causes drift when items are inserted/removed (Terraform reindexes). Use `for_each = toset()`. - **`moved` block required for renaming** — Renaming a resource ID without a `moved {}` block causes destroy + recreate. - **azurerm 4.x renamed attributes** — `allow_blob_public_access` → `allow_nested_items_to_be_public`; `enable_https_traffic_only` → `https_traffic_only_enabled`; `azurerm_app_service` removed → use `azurerm_linux_web_app`. --- ## Reference Index | File | Contents | | ------------------------------------------ | ----------------------------------------------------------------- | | `references/hub-spoke-pattern.md` | Full hub & spoke VNet + peering HCL | | `references/private-endpoint-pattern.md` | PE + DNS zone + VNet link HCL, subresource table | | `references/common-patterns.md` | Diagnostics, conditional deployment, module composition, identity | | `references/budget-pattern.md` | Consumption budget, forecast alerts, anomaly detection | | `references/plan-interpretation.md` | Plan commands, change symbols, red flags, summary script | | `references/avm-pitfalls.md` | Set-type diffs, provider pins, tag ignore, moved blocks, 4.x | | `references/avm-provider-compatibility.md` | AVM-TF module provider/Core constraints, azurerm 5.x revisit | | `references/tf-best-practices-examples.md` | Best-practice code examples, formatting, code review checklist | | `references/bootstrap-backend-template.md` | Backend bootstrap template | | `references/deploy-script-template.md` | Deployment script template | | `references/project-scaffold.md` | Project scaffolding structure | | `references/avm-authoring-requirements.md` | AVM certification: 37 requirements, compliance checklist | | `references/refactor-module.md` | Module extraction, state migration, refactoring patterns | | `references/module-composition.md` | Canonical AVM module composition example with output wiring |