Deploy CSA Loom via Terraform (azurerm_resource_group_template_deployment)¶
Comparative positioning note
This document is written from the perspective of Microsoft Azure, Cloud Scale Analytics, and CSA Loom. Any description of third-party or competing products, services, pricing, or capabilities is derived from publicly available documentation and sources believed accurate at the time of writing, and is provided for general comparison only. We do not claim expertise in, or authority over, any non-Microsoft product or service; the respective vendor's official documentation is the authoritative source for their offerings, which may change over time. Nothing here is intended to disparage any vendor — where a competing product has genuine advantages, we aim to note them honestly. Verify all third-party details against the vendor's current official documentation before making decisions.
For organizations standardized on Terraform / OpenTofu. The Bicep template (platform/fiab/bicep/main.bicep) compiles to ARM JSON which Terraform can drive via the azurerm_subscription_template_deployment resource — no separate Terraform module rewrite needed.
Why this pattern¶
- One source of truth — Bicep stays the canonical template (so upgrades inherit cleanly from upstream releases)
- Terraform state — drift detection + plan/apply lifecycle remains intact for the wrapper resource
- OpenTofu friendly —
azurermworks identically under OpenTofu
If you want a pure Terraform module that mirrors the Loom resource graph, that's tracked in the backlog (no ETA — Bicep is the locked canonical per LD-15).
Prerequisites¶
| Item | Notes |
|---|---|
| Terraform 1.6+ or OpenTofu 1.6+ | |
azurerm provider 3.100+ | |
az CLI for compiling the Bicep to ARM | az bicep build runs locally during the apply |
| SP or workload identity authenticating the provider | OIDC supported |
Module layout¶
terraform/
├── main.tf # the wrapper resource
├── variables.tf
├── outputs.tf
└── arm/
└── main-template.json # generated by az bicep build (gitignored)
main.tf¶
terraform {
required_version = ">= 1.6"
required_providers {
azurerm = { source = "hashicorp/azurerm", version = "~> 3.100" }
}
}
provider "azurerm" {
features {}
use_oidc = true
client_id = var.azure_client_id
tenant_id = var.azure_tenant_id
subscription_id = var.azure_subscription_id
}
# Compile the Loom Bicep to ARM JSON every plan (vendored copy in arm/)
# Run this once before terraform plan:
# az bicep build --file ../platform/fiab/bicep/main.bicep \
# --outfile arm/main-template.json
data "local_file" "arm_template" {
filename = "${path.module}/arm/main-template.json"
}
resource "azurerm_subscription_template_deployment" "loom" {
name = "loom-${var.environment}-${formatdate("YYYYMMDDhhmm", timestamp())}"
location = var.location
template_content = data.local_file.arm_template.content
parameters_content = jsonencode({
loomAdminGroupObjectId = { value = var.admin_group_object_id }
boundary = { value = var.boundary }
capacitySku = { value = var.capacity_sku }
deploymentMode = { value = var.deployment_mode }
hubVnetCidr = { value = var.hub_vnet_cidr }
})
lifecycle {
ignore_changes = [name] # don't churn on timestamp suffix
}
}
variables.tf¶
variable "azure_client_id" { type = string }
variable "azure_tenant_id" { type = string }
variable "azure_subscription_id" { type = string }
variable "environment" {
type = string
description = "dev | stage | prod"
}
variable "location" {
type = string
default = "eastus2"
}
variable "boundary" {
type = string
default = "commercial"
validation {
condition = contains(["commercial", "gcc", "gcc-high"], var.boundary)
error_message = "boundary must be commercial | gcc | gcc-high"
}
}
variable "capacity_sku" { type = string, default = "F8" }
variable "deployment_mode" { type = string, default = "single-sub" }
variable "hub_vnet_cidr" { type = string, default = "10.0.0.0/16" }
variable "admin_group_object_id" { type = string }
outputs.tf¶
output "deployment_name" {
value = azurerm_subscription_template_deployment.loom.name
}
output "outputs" {
value = azurerm_subscription_template_deployment.loom.output_content
sensitive = true
}
Run¶
# One-time: compile Bicep → ARM
az bicep build --file ../platform/fiab/bicep/main.bicep \
--outfile arm/main-template.json
# Standard Terraform flow
terraform init
terraform plan -var environment=dev -var admin_group_object_id=<group-oid>
terraform apply -var environment=dev -var admin_group_object_id=<group-oid>
For OpenTofu, swap terraform for tofu in every command.
Post-deploy bootstrap¶
Terraform won't run the Power BI / Databricks / Dataverse bootstrap. Either chain it as a null_resource with local-exec, or run it out-of-band:
bash ../scripts/csa-loom/bootstrap-all.sh \
--boundary "$TF_VAR_boundary" \
--environment "$TF_VAR_environment"
Related¶
- Bicep CLI direct — skip Terraform entirely
- Azure DevOps — common pattern for federal customers on ADO + Terraform