Terraform Track
The Terraform track delivers a functionally equivalent variant of the SMB Ready Foundation alongside the canonical Bicep track. Partners already standardized on Terraform can adopt the foundation without adding Bicep to their toolchain.
Key Differences from Bicep
Section titled “Key Differences from Bicep”| Aspect | Bicep | Terraform |
|---|---|---|
| Deployment scopes | Two templates: deploy-mg.bicep (MG) + main.bicep (sub) | Single root composes both scopes |
| Orchestration | Pre-provision hook runs MG template, then azd provision | Single azd provision → terraform apply |
| Module system | modules/*.bicep | 17 child modules under modules/ |
| AVM posture | AVM-first (13 modules) | Raw azurerm_* / azapi_resource |
ManagedBy tag | "Bicep" | "Terraform" |
| State management | ARM deployments (stateless) | Remote state in Azure Storage |
Provider Pins
Section titled “Provider Pins”| Component | Pin | Rationale |
|---|---|---|
| terraform | >= 1.9 | Required for import block + mock_provider |
| azurerm | ~> 4.0 | 4.x LTS-equivalent; allows minor upgrades |
| azapi | ~> 2.0 | Needed for Azure Migrate (no azurerm support) |
File Layout
Section titled “File Layout”infra/terraform/smb-ready-foundation/├── azure.yaml # azd manifest (provider: terraform)├── backend.tf # backend "azurerm" {} partial config├── versions.tf, providers.tf├── variables.tf, locals.tf├── main.tf # 17 module calls + root import block├── outputs.tf├── modules/│ ├── management-group/ # MG + subscription association│ ├── policy-assignments-mg/ # 33 MG-scoped policy assignments│ ├── resource-groups/ # 6 resource groups│ ├── network-hub/ # Hub VNet, NSG, 4 subnets, PDZ│ ├── network-spoke/ # Spoke VNet, NSG, optional NAT│ ├── firewall/ # Azure Firewall Basic (conditional)│ ├── route-tables/ # Spoke + gateway UDRs│ ├── vpn-gateway/ # VPN Gateway VpnGw1AZ (conditional)│ ├── peering/ # Hub↔spoke peering│ ├── monitoring/ # Log Analytics Workspace│ ├── backup/ # RSV + DefaultVMPolicy│ ├── policy-backup-auto/ # Sub-scope DINE + role assignments│ ├── migrate/ # Azure Migrate (azapi_resource)│ ├── keyvault/ # Key Vault + PE + diag settings│ ├── automation/ # Automation Account + LAW link│ ├── budget/ # Consumption budget│ └── defender/ # Defender for Cloud Free├── hooks/ # pre/post-provision (bash + PowerShell)├── scripts/ # bootstrap-tf-backend + remove└── tests/ └── scenarios.tftest.hcl # 6 plan-mode scenario testsDeployment Flow
Section titled “Deployment Flow”azd provision ├─ hooks/pre-provision.{sh,ps1} │ 1. Validate OWNER, CIDRs (hub/spoke/on-prem overlap) │ 2. Azure preflight (auth, required RP registration) │ 3. Bootstrap TF backend (RG + storage + container) │ 4. Write terraform.auto.tfvars.json │ 5. Clean stale budget / faulted resources │ 6. terraform init -reconfigure ├─ terraform apply │ - MG + subscription association (with import block) │ - 33 MG-scoped + 1 sub-scoped policy assignments (34 total) │ - 6 resource groups, budget, Defender pricings │ - Hub/spoke VNets, NSGs, subnets, PDZ │ - Conditional: NAT gateway OR firewall + route tables │ - Conditional: VPN gateway (serialized after firewall) │ - Conditional: hub-spoke peering │ - LAW, RSV, policy-backup-auto, Migrate, KV, Automation └─ hooks/post-provision.{sh,ps1} - Terraform outputs summary + next stepsTesting
Section titled “Testing”The Terraform track includes plan-mode tests covering all scenarios:
cd infra/terraform/smb-ready-foundationterraform testSix test runs validate: baseline, firewall, vpn, full, default values, and edge cases. Tests use mock_provider + override_resource — no Azure credentials required.
Validation
Section titled “Validation”terraform fmt -check -recursiveterraform init -backend=false && terraform validatenpm run validate:terraformnpm run validate:iac-security-baseline ADR-0005: Terraform Dual-Track Decision rationale for shipping both Bicep and Terraform
ADR-0006: Single-Root Composition Why Terraform uses a single root instead of two-step deploy