Terraform: Read Multiple Subscriptions

3 minute read

Description:

Use the following code blocks to test reading multiple subscriptions at the same time.

To Resolve:

  1. First, the code, then we can talk about it:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    
    terraform {
    required_providers {
    
       azurerm = {
          source  = "hashicorp/azurerm"
          version = "~>3.10.0"
       }
    
    }
    required_version = "~>1.1.0"
    }
    
    # Subscription 1 will be the default block
    provider "azurerm" {
    client_id                  = var.client_id
    client_secret              = var.client_secret
    subscription_id            = var.subscription_id
    tenant_id                  = var.tenant_id
    skip_provider_registration = true
    features {}
    }
    
    provider "azurerm" {
    client_id     = trimspace(var.client_id)
    client_secret = trimspace(var.client_secret)
    tenant_id     = trimspace(var.tenant_id)
    features {}
    alias                      = "subscription-2"
    skip_provider_registration = true
    subscription_id            = "xxxx-xxxxx-xxxxx"
    }
    
    variable "tenant_id" {
    description = "(Required) Service Principal AD Tenant ID - Azure AD for terraform authentication."
    type        = string
    }
    
    variable "subscription_id" {
    description = "(Required) Azure Subscription Id used to connect to AzureRM provider."
    type        = string
    }
    
    variable "client_id" {
    description = "(Required) Service Principal App ID - Azure AD for terraform authentication."
    type        = string
    }
    
    variable "client_secret" {
    description = "(Required) Service Principal Client Secret - Azure AD for terraform authentication."
    type        = string
    }
    
    data "azurerm_resources" "storageaccount" {
    resource_group_name = "sub1-storage-rg"
    type                = "Microsoft.Storage/storageAccounts"
    required_tags = {
       Purpose = "Mgmt"
    }
    }
    
    output "data" {
    value = data.azurerm_resources.storageaccount.resources[0].id
    }
    
    data "azurerm_resources" "storageaccount_sub2" {
    resource_group_name = "sub2-storage-rg"
    type                = "Microsoft.Storage/storageAccounts"
    required_tags = {
       Purpose = "Mgmt"
    }
    provider =  azurerm.subscription-2
    }
    
    output "data_2" {
    value = data.azurerm_resources.storageaccount_sub2.resources[0].id
    }
    
  2. Run:

    1
    2
    3
    
    cd C:\scripts\tf\local-state
    terraform init
    terraform plan -var-file="env.tfvars" -out="tf.plan"
    
  3. Output:

    1
    2
    3
    4
    5
    6
    7
    
    > terraform plan -var-file="env.tfvars" -out="tf.plan"
    
    Changes to Outputs:
    + data     = "/subscriptions/***/storage1"
    + data_2   = "/subscriptions/***/storage2"
    
    You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.
    
  4. So what is happening?

    • First, there are two provider blocks but only 1 has an alias parameter. Terraform will use the first provider when it queries data "azurerm_resources" "storageaccount" because that is the default provider.
    • Then, it will use the second provider when it queries data "azurerm_resources" "storageaccount_sub2" because we provided provider = azurerm.subscription-2 to it.
    • That’s it really, the only gotcha with providers is that sometimes you have to pass them as a map object like when you call a module but when you call a data or resource you pass it as an argument. For example:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    ## Calling module has two provider blocks like the example above: one with alias `spoke-subscription` and the other other with alias `hub-subscription`
    module "learning-subnet" {
    source                                      = "git::https://some/path/terraform-modules//Subnet?ref=v4.1.0"
    <..> # other arguments removed for brevity
    providers = {
       azurerm.spoke = azurerm.spoke-subscription
       azurerm.hub    = azurerm.hub-subscription
    }
    }
    
    • NOTE: Remember that a parameter is a function definition and an argument is the value passed to it. MSDN references like a parameter is a ‘P’arking space and an argument is an ‘A’utomobile. Many different automobiles can fit into a single parking space, i.e. different arguments can be passed to a single defined parameter.
    • Then in the subnet module, it will have preset provider block defined that expect to be passed in:
    1
    2
    3
    4
    5
    6
    7
    
    provider "azurerm" {
    alias = "spoke"
    }
    
    provider "azurerm" {
    alias = "hub"
    }
    
    • We may be able to rewrite the subnet module to read data blocks instead, might need to follow up on this. Passing providers seems to be working well enough though.