ADR-0006: Single-Root Composition
Status: Implemented | Date: 2026-04-17
Context
Section titled “Context”Bicep requires two separate templates because a template can only have one targetScope:
deploy-mg.bicep(targetScope = 'managementGroup') — MG creation + 33 policy assignmentsmain.bicep(targetScope = 'subscription') — everything else
Terraform has no such restriction. A single root can declare azurerm_management_group, azurerm_management_group_policy_assignment, and azurerm_resource_group resources in the same configuration. The question: keep the two-step split for symmetry, or collapse?
Decision
Section titled “Decision”Collapse into a single root with 17 child modules. Management group, policy assignments, and subscription-scope resources all live in one Terraform configuration.
Key Implementation Details
Section titled “Key Implementation Details”- Root-level
importblock: Targetsazurerm_management_group.smb_rfso thatterraform applysucceeds regardless of whether the MG pre-exists (created by Bicep or a prior run) lifecycle.ignore_changes = [subscription_ids]: Prevents drift when subscriptions are manually moved- Single state file:
smb-ready-foundation.tfstatein the bootstrapped Azure Storage backend - Module orchestration:
main.tfcomposes 17 module calls with explicitdepends_onfor ordering
Consequences
Section titled “Consequences”Positive: Single terraform apply instead of two-step orchestration, simpler CI/CD pipeline, one state file to manage, no hook-based scope handoff.
Negative: Larger blast radius per apply (MG changes and resource changes in one operation), slightly more complex main.tf with cross-scope dependencies.