terraformazure-rm

Loop through a Terraform data source results for ip_rule block (azurerm_container_registry)


I want to loop through the results of a (looped) data source that gets my VM IPs and add them to an IP rule on an Azure Container Registry. Neither approach here works but I feel like something close should but can't find a way. The data source is working fine. Its just using for_each to loop through and add the IPs to the ACR firewall that I'm having trouble with.

variable "vmips" {
  type = map(any)
  default = {
    "vm1" = "rg1",
    "vm2" = "rg2
  } 
}

data "azurerm_public_ip" "vmips" {
  for_each            = var.vmips
  name                = each.key
  resource_group_name = each.value
}

resource "azurerm_container_registry" "acr" {
  name                          = "MyAcR"
  resource_group_name           = "myrg"
  location                      = "uksouth"
  sku                           = "Premium"
  admin_enabled                 = false
  public_network_access_enabled = false

  network_rule_set {
    default_action = "Deny"
    
    ip_rule {
      for_each  = var.vmips
      action    = "Allow"
      ip_range  = data.azurerm_public_ip.vmips[each.key].ip_address
    }
    # or
    dynamic "ip_rule" {
      for_each  = var.vmips
      content {
        action    = "Allow"
        ip_range  = data.azurerm_public_ip.vmips[each.key].ip_address
      }
    }
  }
}

Solution

  • When you use dynamic blocks the context of iteration is limited to the block itself. In your use case particularly network_rule_set can be only one block as per documentation however, ip_rule can be one or more blocks.

    The below code can be used to deploy the acr with multiple ip_rules as you intend.

    variable "vmips" {
      type = map(string)
      default = {
        "vm01" = "stackoverflow-ip-01", ## value can be rg as you intend when using data source.
        "vm02" = "stackoverflow-ip-02", ## value can be rg as you intend when using data source.
      }
    }
    
    resource "azurerm_resource_group" "stackoverflow" {
      name     = "stackoverflow-rg"
      location = "West Europe"
    }
    ## I have used normal resource but the data source will be exactly the same with correct referencing.
    resource "azurerm_public_ip" "vmips" {
      for_each = var.vmips
    
      name                = each.value
      resource_group_name = azurerm_resource_group.stackoverflow.name
      location            = azurerm_resource_group.stackoverflow.location
      allocation_method   = "Static"
    }
    
    
    
    resource "azurerm_container_registry" "acr" {
      name                          = "mymostuniqueacr001" ## has to be globally unique
      resource_group_name           = azurerm_resource_group.stackoverflow.name ## adjust accordingly 
      location                      = azurerm_resource_group.stackoverflow.location adjust accordingly 
      sku                           = "Premium"
      admin_enabled                 = false
      public_network_access_enabled = false
    
    ### This is the interesting section ###
      dynamic "network_rule_set" {
        for_each = length(var.vmips) > 0 ? ["only_one_network_rule_set_is_allowed"] : []
    
        content {
          default_action = "Deny"
    
          dynamic "ip_rule" {
            for_each = var.vmips
            content {
              action   = "Allow"
              ip_range = azurerm_public_ip.vmips[ip_rule.key].ip_address
            }
          }
        }
      }
    }
    

    official documentation for dynamic blocks: https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks

    Apart from that there seems to be a copy-paste error too in your variable definition.

    variable "vmips" {
      type = map(any)
      default = {
        "vm1" = "rg1",
        "vm2" = "rg2" ### " is missing ###
      } 
    }
    

    Hope it helps :)