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

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 unique

Running this code will deploy two RGs into two seperate subscriptions:

I have intentionally left the end of the sub-ids unblurred to show you that there is indeed two different ones

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

Terraform Registry
GitHub - carlzxc71/azapi-subscription-deployment: This repository contains some demo code for configuring resources in multiple Azure Subscriptions with only one provider block defined
This repository contains some demo code for configuring resources in multiple Azure Subscriptions with only one provider block defined - carlzxc71/azapi-subscription-deployment

About me

About me
If you have landed on my page you will have already understood my passion for tech, but obviously there is more to life than that. Here I will try and outline a few of my other hobbies. Strength training I am a person who loves to move around and challenge