Deploying to multiple Azure subscriptions with Terraform, but only one provider block

If you are deploying Azure resources you are most likely doing so using the azurerm
provider which requires you to supply a subscription_id
and Terraform will run in the context of this subscription both from your local server and from your pipeline runners. Imagine that you have a CICD pipeline that runs and you want to deploy a resource-group to the two different subscriptions that you have control over in your environment. By default, any resource groups you specify in your configuration will only be deployed inside the subscription of your current context, ie whatever subscription ID you enter into your provider block. No RGs get created in any of the other subscriptions at this point.
This is usually solved by defining more than one provider
block with an alias:
provider "azurerm" {
subscription_id = "<subscription1-id>"
features {}
}
provider "azurerm" {
subscription_id = "<subscription2-id>"
features {}
alias = "second-subscription-name"
}
And inside your resource group resource block you can define which provider to use:
resource "azurerm_resource_group" "rg1" {
name = "rg-prod-sc-api"
location = var.location
}
resource "azurerm_resource_group" "rg2" {
name = "rg-prod-sc-api"
location = var.location
provider = azurerm.second-subscription-name
}
Final product:

This is fine but I feel like this does not scale well if you have a dozen more subscriptions in your environment. It will be a lot of code and as far as I can tell you cannot actually run any for_each
loops in a good way over providers
- perhaps if you modularize it it could work somehow? I've yet to delve into any other solutions for that.
Another solution
Introducing the AzAPI
provider into the mix! Any azapi_resource
accepts a property called parent_id
which I intend to loop over. So I have defined my two different subscriptions here in a locals
block that I can use to add and remove subscriptions to and this will ensure this one singular resource
block gets deployed to each subscription, essentially granting me control over several subscriptions with just one provider
block.
First I define my main.tf
with a locals
that will serve as my database of subscription names and IDs I want to control:
terraform {
required_providers {
azapi = {
source = "Azure/azapi"
version = "2.0.1"
}
}
}
provider "azapi" {}
locals {
list_of_subscriptions = [
{
name = "Development Subscription"
id = "<sub-id>"
},
{
name = "Production Subscription"
id = "<sub-id>"
}
]
}
I will then define my singlar resource group block which contains a loop that will iterate over all entries into my list of objects:
resource "azapi_resource" "rg" {
for_each = { for subscription in local.list_of_subscriptions : subscription.id => subscription }
type = "Microsoft.Resources/resourceGroups@2024-03-01"
name = "rg-${var.environment}-${var.location_short}-azapi"
parent_id = "/subscriptions/${each.key}"
location = var.location
}
The end product:

each.key
= the subscription ID from the locals, which will always be uniqueRunning this code will deploy two RGs into two seperate subscriptions:

And in the portal I can see two RGs in different subscriptions:

Conclusion
Not only is the AzAPI
provider incredibly fast it will also have support for the most cutting-edge resource even before there is an official azurerm
resource for it since it talks directly to the Azure ARM API.
This way I could create configuration to control configuration in different Azure Subscriptions for central resources with unique configuration for each environment as long as I write my locals
or in most cases my variables
block correctly. For example you could imaging if we have a central networking module that we use to manage VNETs across subscriptions, that could look like this perhaps:

Now this is not perfect nor tested code just a mockup supposed to spark some ideas, I would for example use some more robust modules to deal with networks in my environment, perhaps you have a better idea?
Reference
About me
