Search code examples
azureterraformazure-front-door

Nested loops within terraform module for Azure FrontDoor


I'm trying to deploy a list of Azure Front Doors and their custom https configuration resources. I have a list of Azure Front Door resources which are deployed like this pseudo code. They work correctly (although without custom https configuration)

resource "azurerm_frontdoor" "front_door" {
  count = length(local.frontdoors)
... config
}

I then try and add some terraform to create the custom https configuration as described here and useterraform azure frontdoor custom https config docs the following fragment:

resource "azurerm_frontdoor_custom_https_configuration" "custom_https_configuration" {
  count                             = length(local.frontdoors)
  for_each                          = { for frontend in azurerm_frontdoor.front_door[count.index].frontend_endpoint : frontend.id => frontend_id }
  frontend_endpoint_id              = each.value.frontend_id
  custom_https_provisioning_enabled = each.key != "front_door" ? local.frontend_https_configurations[each.key].custom_https_provisioning_enabled : false
  dynamic "custom_https_configuration" {
    for_each = (each.key != "front_door" ? local.frontend_https_configurations[each.key].custom_https_provisioning_enabled : false) ? [1] : []
    content {
      certificate_source                         = "AzureKeyVault"
      azure_key_vault_certificate_secret_name    = XXXX
      azure_key_vault_certificate_secret_version = XXXX
      azure_key_vault_certificate_vault_id       = XXXX
    }
  }
}

I'm getting this syntax error:

Error: Invalid combination of "count" and "for_each"

if i try and remove the count, and use the for_each structure instead:

resource "azurerm_frontdoor_custom_https_configuration" "custom_https_configuration" {
  for_each                          = { 
      for frontdoor in azurerm_frontdoor.front_door :
      [
        for key, value in frontdoor.frontend_endpoint: value.frontend.id => frontend_id
      ]
  }
  frontend_endpoint_id              = each.value.frontend_id
  custom_https_provisioning_enabled = each.key != "front_door" ? local.frontend_https_configurations[each.key].custom_https_provisioning_enabled : false
  dynamic "custom_https_configuration" {
    for_each = (each.key != "front_door" ? local.frontend_https_configurations[each.key].custom_https_provisioning_enabled : false) ? [1] : []
    content {
      certificate_source                         = "AzureKeyVault"
      azure_key_vault_certificate_secret_name    = XXXX
      azure_key_vault_certificate_secret_version = XXXX
      azure_key_vault_certificate_vault_id       = XXXX
    }
  }
}

I get this error instead:

Error: Invalid 'for' expression

on main.tf line 25, in resource "azurerm_frontdoor_custom_https_configuration" "custom_https_configuration": 173: for_each = { 174: for frontdoor in azurerm_frontdoor.front_door : 175: [ 176: for key, value in frontdoor.frontend_endpoint: value.frontend.id => frontend_id 177: ] 178: }

Key expression is required when building an object.

How can i have a nested loop so that i can successfully deploy a f


Solution

  • When you need to nest for loops, you need to use the flatten function (for lists) or the merge function in combination with the ... list expansion operator (for maps).

    Basically, like so:

    // To make a list
    for_each = flatten([
      for idx1, val1 in var.list1:
      [
        for idx2, val2 in val2.list_field:
          // Here is where you construct whatever value/object for each element
      ]
    ])
    
    // To make a list
    for_each = merge([
      for key1, val1 in var.map1:
      {
        for key2, val2 in val1.map_field:
        // Some key/value pair, such as:
        "${key1}-${key2}" => val2
      }
    ]...)
    

    You also have your map comprehension key/value reversed. Try this:

    for_each = merge([ 
          for idx, frontdoor in azurerm_frontdoor.front_door :
          {
            for key, value in frontdoor.frontend_endpoints: 
            "${idx}-${key}" => {
               endpoint_key = key
               endpoint_id = value
            }
          }
      ]...)
    

    And now within your resource, you can use each.value.endpoint_key and each.value.endpoint_id.