Terraform Generation
Generate Terraform IaC files from the approved infrastructure plan.
File Structure
Generate files under <project-root>/infra/:
infra/
├── main.tf # Root module — calls child modules
├── variables.tf # Input variable declarations
├── outputs.tf # Output values
├── terraform.tfvars # Default variable values
├── providers.tf # Provider configuration
├── backend.tf # State backend configuration
└── modules/
├── storage/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── compute/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── networking/
├── main.tf
├── variables.tf
└── outputs.tfGeneration Steps
- Create
infra/directory — create<project-root>/infra/and<project-root>/infra/modules/directories. All files in subsequent steps go here. - Read plan — load
<project-root>/.azure/infrastructure-plan.json, verifymeta.status === "approved" - Generate providers.tf — write
infra/providers.tfto configureazurermprovider with required features - Generate modules — group resources by category; one module per group under
infra/modules/ - Generate root main.tf — write
infra/main.tfthat calls all modules, wire outputs to inputs - Generate variables.tf — write
infra/variables.tfwith all configurable parameters - Generate terraform.tfvars — write
infra/terraform.tfvarswith default values from the plan - Generate backend.tf — write
infra/backend.tffor Azure Storage backend remote state
Terraform Conventions
- Use
azurermprovider (latest stable version) - Set
features {}block in provider configuration - Use
variableblocks withdescription,type, anddefaultwhere appropriate - Use
localsfor computed values and naming patterns - Use
depends_ononly when implicit dependencies are insufficient - Tag all resources with
environment,workload, andmanaged-by = "terraform"
Provider Configuration
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}Multi-Environment
For multi-environment plans, generate one .tfvars file per environment:
infra/
├── main.tf
├── variables.tf
├── dev.tfvars
├── staging.tfvars
└── prod.tfvarsDeploy with: terraform apply -var-file=prod.tfvars
Validation Before Deployment
Run terraform validate and terraform plan to verify before applying.
Correctness Checklist (must pass terraform validate with zero errors)
Generate against these rules, then run terraform init -backend=false + terraform validate and fix
in-place until clean. These are the failures that most often break validation:
- Every referenced value is declared. Each
var.Xhas avariable "X"block; eachlocal.Xis defined; eachmodule.X/azurerm_*.Xreference exists. No references to undeclared symbols. - Module wiring is complete. Values passed into a child module map to declared
variableblocks in that module; values read asmodule.X.Ymap to declaredoutput "Y"in that child module. - Existing resources use
data/import, not newresource. Reference pre-existing infra viadatasources (orimport), and wire new resources to them — never recreate them. - Valid provider + required attributes.
required_providerspinsazurerm(~> 4.0), theprovider "azurerm"block hasfeatures {}, and every resource sets its required arguments with valid enum values and correctly-typed attributes. - Correct block vs. attribute syntax. Nested blocks (e.g.
identity,site_config,ip_configuration) use block syntax; scalars use=. No unsupported/renamed arguments for the pinned provider version. tfvarsmatch variables. Every value interraform.tfvars/*.tfvarscorresponds to a declaredvariable; every variable without a default is supplied.- No secrets in code. Secrets come from variables (
sensitive = true) or Key Vault data sources, never hardcoded literals.
If terraform is unavailable, self-review every item above before presenting.