Search code examples
azureterraformazure-rm

Terraform on Azure - I am trying to call the results of a for_each block in one resource in another resource


Terraform on Azure - I am trying to call the results of a for_each block in one resource in another resource.

I have gone through the following & cant seem to be able to wrap my head around the logic.

https://stackoverflow.com/questions/71646136/terraform-reference-a-for-each-resource-from-another-for-each-resource

https://stackoverflow.com/questions/68571073/for-each-loop-in-resource-block-terraform

I have 2 resource blocks [I have added the locals file ]

# Local Variables
#
locals {
  # Create a flattened list for resource groups in multiple regions
  multi_region_rg = flatten([
    for rg_key, rg in var.multiple_resource_groups : [
      for region_key, region in var.regions : {
        rg_name     = var.environment != "" ? format("%s-rg-%s-%s-%s", var.tenant, rg_key, var.environment, region.code) : format("%s-rg-%s-%s", var.tenant, rg_key, region.code)
        region_name = region.name
      }
    ]
  ])
}

# Local Variables
#
locals {
  # Create a flattened list for resource groups in multiple regions
  multi_region_vnet = flatten([
    for vnet_key, vnet in var.multiple_vnets : [
      for region_key, region in var.regions : {
        vnet_name                     = var.environment != "" ? format("%s-rg-%s-%s-%s", var.tenant, vnet_key, var.environment, region.code) : format("%s-rg-%s-%s", var.tenant, vnet_key, region.code)
        region_name                   = region.name
        virtual_network_address_space = vnet.address_space
      }
    ]
  ])
}

TFVARS file

# Global tags
global_tags = { "ManagedBy" = "Terraform" }
# Map of regions for deployment of resource groups
regions = {
  region1 = { name = "australiaeast", code = "ae" },
  region2 = { name = "australiasoutheast", code = "ase" }
}
# Tenant
tenant = "cubem_"


# Map of resource groups
# NOTE: Use short names or aliases for resource groups as the code will generate the resource group name as per the following format:
#       <tenant>-rg-<resource_group_alias>-<environment>-<region_code>

multiple_resource_groups = {
  connectivity = {

    tags = {
      
      CostCenter = "NETWORKS",
      Department = "Network Services"
    }

  }
}

###################################### Vnet Values #######################

# Map of resource groups
# NOTE: Use short names or aliases for resource groups as the code will generate the resource group name as per the following format:
#       <tenant>-rg-<resource_group_alias>-<environment>-<region_code>

multiple_vnets = {
  vnetx = { address_space = ["10.1.0.0/24"] },
 
}

# Create 2 resource groups in 2 regions [1 in each region]
#
resource "azurerm_resource_group" "rg" {
  for_each = {
    for k, v in local.multi_region_rg : v.rg_name => v
  }
  name     = each.key
  location = each.value.region_name
  
}
# Create 2 vnets   in 2 regions [1 in each region]
#

resource "azurerm_virtual_network" "vnet" {
  for_each = {
    for k, v in local.multi_region_vnet : v.vnet_name => v
  }
  name                = each.key
  location            = each.value.region_name
  address_space       = each.value.virtual_network_address_space
  resource_group_name = azurerm_resource_group.rg[each.key]  #Im trying to call the results of the resource group block above 

}

These blocks work well when they are run independently but I want to have the ability to run them concurrently so that the vnets gets deployed into the resource groups that are created.

If I use this line resource_group_name = azurerm_resource_group.rg[each.key] its throwing this error


PS C:\PycharmProjects\terraform-projects\my_azure_connectivity_ref_repo\hub-and-spoke\modules\vnet> terraform plan  
╷
│ Error: Invalid index
│ 
│   on main.tf line 69, in resource "azurerm_virtual_network" "vnet":
│   69:   resource_group_name = azurerm_resource_group.rg[each.key]
│     ├────────────────
│     │ azurerm_resource_group.rg is object with 2 attributes
│     │ each.key is "rg-vnetx-ae"
│ 
│ The given key does not identify an element in this collection value.
╵
╷
│ Error: Invalid index
│
│   on main.tf line 69, in resource "azurerm_virtual_network" "vnet":
│   69:   resource_group_name = azurerm_resource_group.rg[each.key]
│     ├────────────────
│     │ azurerm_resource_group.rg is object with 2 attributes
│     │ each.key is "rg-vnetx-ase"
│
│ The given key does not identify an element in this collection value.
╵
PS C:\PycharmProjects\terraform-projects\my_azure_connectivity_ref_repo\hub-and-spoke\modules\vnet> 


Solution

  • There are two issues in your resource "azurerm_virtual_network" "vnet" definition in the resource_group_name attribute.

    • [each.key] in azurerm_resource_group.rg[each.key] expression does not exists. here [each.key] is the vnet name actually coming from for_each = { for k, v in local.multi_region_vnet : v.vnet_name => v} which is not available in the resource azurerm_resource_group.rg
    > { for k, v in local.multi_region_vnet : v.vnet_name => v }
    {
      "test-rg-vnet_key-dev-ae" = {
        "region_name" = "australiaeast"
        "virtual_network_address_space" = [
          "10.1.0.0/24",
        ]
        "vnet_name" = "test-rg-vnet_key-dev-ae"
      }
      "test-rg-vnet_key-dev-ase" = {
        "region_name" = "australiasoutheast"
        "virtual_network_address_space" = [
          "10.1.0.0/24",
        ]
        "vnet_name" = "test-rg-vnet_key-dev-ase"
      }
    }
    
    • The other issue also lies in the resource_group_name attribute of vnet resource with the reference. in spite of azurerm_resource_group.rg[$(expression)] should be azurerm_resource_group.rg[$(expression)].name

    Hence, You have to adjust either of your resources with a standard key which is common for both resource_group and vnet. I have done it in the resource group with location/region as it is required in both of the resources.

    Hence the correct code addressing both the above issues would be.

    ## key is now region_name in azurerm_resource_group  ##
    
    resource "azurerm_resource_group" "rg" {
      for_each = {
        for k, v in local.multi_region_rg : v.region_name => v
      }
      name     = each.value.rg_name
      location = each.key
    
    }
    
    resource "azurerm_virtual_network" "vnet" {
      for_each = {
        for k, v in local.multi_region_vnet : v.vnet_name => v
      }
      name                = each.key
      location            = each.value.region_name
      address_space       = each.value.virtual_network_address_space
      resource_group_name = azurerm_resource_group.rg[(each.value.region_name)].name
    }
    
    

    Code In Action

    
    ➜  random_local_tests git:(main) ✗ terraform apply -auto-approve 
    
    Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
      + create
    
    Terraform will perform the following actions:
    
      # azurerm_resource_group.rg["australiaeast"] will be created
      + resource "azurerm_resource_group" "rg" {
          + id       = (known after apply)
          + location = "australiaeast"
          + name     = "test-rg-vnet_key-dev-ae"
        }
    
      # azurerm_resource_group.rg["australiasoutheast"] will be created
      + resource "azurerm_resource_group" "rg" {
          + id       = (known after apply)
          + location = "australiasoutheast"
          + name     = "test-rg-vnet_key-dev-ase"
        }
    
      # azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ae"] will be created
      + resource "azurerm_virtual_network" "vnet" {
          + address_space       = [
              + "10.1.0.0/24",
            ]
          + dns_servers         = (known after apply)
          + guid                = (known after apply)
          + id                  = (known after apply)
          + location            = "australiaeast"
          + name                = "test-rg-vnet_key-dev-ae"
          + resource_group_name = "test-rg-vnet_key-dev-ae"
          + subnet              = (known after apply)
        }
    
      # azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ase"] will be created
      + resource "azurerm_virtual_network" "vnet" {
          + address_space       = [
              + "10.1.0.0/24",
            ]
          + dns_servers         = (known after apply)
          + guid                = (known after apply)
          + id                  = (known after apply)
          + location            = "australiasoutheast"
          + name                = "test-rg-vnet_key-dev-ase"
          + resource_group_name = "test-rg-vnet_key-dev-ase"
          + subnet              = (known after apply)
        }
    
    Plan: 4 to add, 0 to change, 0 to destroy.
    azurerm_resource_group.rg["australiaeast"]: Creating...
    azurerm_resource_group.rg["australiasoutheast"]: Creating...
    azurerm_resource_group.rg["australiaeast"]: Creation complete after 5s [id=/subscriptions/xxxxxxxxxxxx/resourceGroups/test-rg-vnet_key-dev-ae]
    azurerm_resource_group.rg["australiasoutheast"]: Creation complete after 6s [id=/subscriptions/xxxxxxxxxxxx/resourceGroups/test-rg-vnet_key-dev-ase]
    azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ae"]: Creating...
    azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ase"]: Creating...
    azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ase"]: Still creating... [10s elapsed]
    azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ae"]: Still creating... [10s elapsed]
    azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ae"]: Creation complete after 18s [id=/subscriptions/xxxxxxxxxxxx/resourceGroups/test-rg-vnet_key-dev-ae/providers/Microsoft.Network/virtualNetworks/test-rg-vnet_key-dev-ae]
    azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ase"]: Creation complete after 19s [id=/subscriptions/xxxxxxxxxxxx/resourceGroups/test-rg-vnet_key-dev-ase/providers/Microsoft.Network/virtualNetworks/test-rg-vnet_key-dev-ase]
    
    Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
    

    Hope it helps.

    And additional info which is not relevant here too much I have used static values instead of some variables.

    rg_name = var.environment != "" ? format("%s-rg-%s-%s-%s", "test", "vnet_key", var.environment, region.code) : format("%s-rg-%s-%s", "test", "vnet_key", region.code)
    
    vnet_name = var.environment != "" ? format("%s-rg-%s-%s-%s", "test", "vnet_key", var.environment, region.code) : format("%s-rg-%s-%s", "test", "vnet_key", region.code)