skip to Main Content

In my terraform azure code, when I run the plan, it says it will replace the resource but I can’t seem to find the issue.

My code creates load balancer, lb rule, lb probe, backend pools etc. The error is specifically in the below block

Wherever it’s calling data azurerm_lb_backend_address_pool, it’s saying ‘known after apply’ and recreating it


 locals {
   backend_pool_add = flatten([
    for lb in var.load_balancer: 
    [
      for vm in lb.backend_pool:
      [
          for ip in vm.ip_address:
          {
            lb_name                             = lb.lb_name
            backend_address_pool_name           = vm.backend_address_pool_name
            ip_address                          = ip
            nic_name = vm.ip_address != null ? "nic" : null
          } 
      ] if vm.vm_nic_name == null
    ] 
  ])

  backend_pool_add_ori = flatten([
    for lb in var.load_balancer: 
    [
      for vm in lb.backend_pool:
          {
            lb_name                             = lb.lb_name
            backend_address_pool_name           = vm.backend_address_pool_name
            nic_name = vm.ip_address != null ? "nic" : null
          } 
          if vm.vm_nic_name == null
    ]
  ])   }


data "azurerm_lb_backend_address_pool" "bap_pool_ipname_ori" {
  for_each = { for bap in local.backend_pool_add_ori : "${bap.backend_address_pool_name}-${bap.lb_name}" => bap if bap.nic_name != null}
  name                =  each.value.backend_address_pool_name
  loadbalancer_id     =  azurerm_lb.lb[each.value.lb_name].id
}

resource "azurerm_lb_backend_address_pool_address" "bap_address" {
  for_each = { for idx , bp in local.backend_pool_add : idx => bp if bp.nic_name != null}
  name = data.azurerm_lb_backend_address_pool.bap_pool_ipname_ori["${each.value.backend_address_pool_name}-${each.value.lb_name}"].backend_address[each.key % 2].name
  backend_address_pool_id = azurerm_lb_backend_address_pool.bap_pool_ip[each.value.ip_address].id
  virtual_network_id      = data.azurerm_virtual_network.vnet_backend_address_pool[each.value.lb_name].id
  ip_address              = each.value.ip_address

depends_on = [ azurerm_lb.lb, azurerm_lb_backend_address_pool.bap_pool_ip]

}

I tried to get the output of data block and that too says "known after apply"

Issue:

        "  # module.load_balancer.azurerm_lb_backend_address_pool_address.bap_address[\"3\"] must be replaced",
        "-/+ resource \"azurerm_lb_backend_address_pool_address\" \"bap_address\" {",
        "      ~ id                            = \"/subscriptions/subscription_id/resourceGroups/rg-smartops-prod-att-appaccess-prod-nx01/providers/Microsoft.Network/loadBalancers/lb_name/backendAddressPools/pool_name/addresses/xxxxx-xx-yyyy\" -> (known after apply)",
        "      ~ inbound_nat_rule_port_mapping = [] -> (known after apply)",
        "      ~ name                          = \"xxxxx-xx-xxx-yyyyy\" -> (known after apply) # forces replacement",
        "        # (3 unchanged attributes hidden)",
        "    }",

Expectation:
No Changes.

Please let me know how it can be solved and if any details required

2

Answers


  1. This typically happens when using generic indices within your maps (like "0","1","2",etc) as the order in which those are processed might shift due to certain circumstances. In your example – code is complaining about module.load_balancer.azurerm_lb_backend_address_pool_address.bap_address[\"3\"] which is a good indication if this behaviour and it signals that the resource previously stored at the third index position might be different than the one it’s trying to store now.

    My suggestion would be to switch to iterate that resource through a named index structure and go for something like (similar to what you’ve done in the data):

    resource "azurerm_lb_backend_address_pool_address" "bap_address" {
      for_each = tomap({ 
        for bp in local.backend_pool_add : "${bp.lb_name}" => bp if bp.nic_name != null 
      })
      ......
    

    On another note — would help to share the original var.load_balancer to help troubleshoot better. And out of curiosity – can you not reuse the same local for both the data and resource? Seems the structure and information will be identical between backend_pool_add and backend_pool_add_ori and you could simply use the one local var and reference whatever you need from it.

    Login or Signup to reply.
  2. TL;DR Avoid using a for_each loop with an index – if the order of the elements in the array/list changes, it might recreate resources.

    Workaround: use a map instead, based on a property that is unique.

    Example 1 – using for_each with an index

    Creating resources based on an index:

    locals {
      resources = [
        {
          name = "foo",
          description = "This is foo"
        },
        {
          name = "bar",
          description = "This is bar"
        },
        {
          name = "baz",
          description = "This is baz"
        }
      ]
    }
    
    resource "null_resource" "name" {
      for_each = { for idx, r in local.resources : idx => r }
    
      triggers = {
        name        = each.value.name
        description = each.value.description
      }
    }
    

    Running terraform apply:

    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:
    
      # null_resource.name["0"] will be created
      + resource "null_resource" "name" {
          + id       = (known after apply)
          + triggers = {
              + "description" = "This is foo"
              + "name"        = "foo"
            }
        }
    
      # null_resource.name["1"] will be created
      + resource "null_resource" "name" {
          + id       = (known after apply)
          + triggers = {
              + "description" = "This is bar"
              + "name"        = "bar"
            }
        }
    
      # null_resource.name["2"] will be created
      + resource "null_resource" "name" {
          + id       = (known after apply)
          + triggers = {
              + "description" = "This is baz"
              + "name"        = "baz"
            }
        }
    
    Plan: 3 to add, 0 to change, 0 to destroy.
    null_resource.name["2"]: Creating...
    null_resource.name["1"]: Creating...
    null_resource.name["0"]: Creating...
    null_resource.name["2"]: Creation complete after 0s [id=5121945913945696620]
    null_resource.name["1"]: Creation complete after 0s [id=9200477420798518525]
    null_resource.name["0"]: Creation complete after 0s [id=8988985286928125209]
    

    Resources were created based on the index – e.g. null_resource.name["0"].

    Let’s now change the order of the array:

    locals {
      resources = [
        {
          name = "foo",
          description = "This is foo"
        },
        {
          name = "baz",
          description = "This is baz"
        },
        {
          name = "bar",
          description = "This is bar"
        }
      ]
    }
    
    resource "null_resource" "name" {
      for_each = { for idx, r in local.resources : idx => r }
    
      triggers = {
        name        = each.value.name
        description = each.value.description
      }
    }
    

    Running terraform plan:

    Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
    -/+ destroy and then create replacement
    
    Terraform will perform the following actions:
    
      # null_resource.name["1"] must be replaced
    -/+ resource "null_resource" "name" {
          ~ id       = "9200477420798518525" -> (known after apply)
          ~ triggers = { # forces replacement
              ~ "description" = "This is bar" -> "This is baz"
              ~ "name"        = "bar" -> "baz"
            }
        }
    
      # null_resource.name["2"] must be replaced
    -/+ resource "null_resource" "name" {
          ~ id       = "5121945913945696620" -> (known after apply)
          ~ triggers = { # forces replacement
              ~ "description" = "This is baz" -> "This is bar"
              ~ "name"        = "baz" -> "bar"
            }
        }
    
    Plan: 2 to add, 0 to change, 2 to destroy.
    

    Example 2 – using for_each with a map

    Creating resources using a map:

    locals {
      resources = [
        {
          name = "foo",
          description = "This is foo"
        },
        {
          name = "bar",
          description = "This is bar"
        },
        {
          name = "baz",
          description = "This is baz"
        }
      ]
    }
    
    resource "null_resource" "name" {
      # Create map with name as key and resource as value
      for_each = { for r in local.resources : r.name => r }
    
      triggers = {
        name        = each.value.name
        description = each.value.description
      }
    }
    

    Running terraform apply:

    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:
    
      # null_resource.name["bar"] will be created
      + resource "null_resource" "name" {
          + id       = (known after apply)
          + triggers = {
              + "description" = "This is bar"
              + "name"        = "bar"
            }
        }
    
      # null_resource.name["baz"] will be created
      + resource "null_resource" "name" {
          + id       = (known after apply)
          + triggers = {
              + "description" = "This is baz"
              + "name"        = "baz"
            }
        }
    
      # null_resource.name["foo"] will be created
      + resource "null_resource" "name" {
          + id       = (known after apply)
          + triggers = {
              + "description" = "This is foo"
              + "name"        = "foo"
            }
        }
    
    Plan: 3 to add, 0 to change, 0 to destroy.
    null_resource.name["foo"]: Creating...
    null_resource.name["bar"]: Creating...
    null_resource.name["baz"]: Creating...
    null_resource.name["foo"]: Creation complete after 0s [id=4638368496288058122]
    null_resource.name["baz"]: Creation complete after 0s [id=7923927469678641556]
    null_resource.name["bar"]: Creation complete after 0s [id=5385743772651277751]
    

    Resources were created based on the map key – e.g. null_resource.name["foo"].

    Let’s now change the order of the elements:

    locals {
      resources = [
        {
          name = "baz",
          description = "This is baz"
        },
        {
          name = "foo",
          description = "This is foo"
        },
        {
          name = "bar",
          description = "This is bar"
        }
      ]
    }
    
    resource "null_resource" "name" {
      # Create map with name as key and resource as value
      for_each = { for r in local.resources : r.name => r }
    
      triggers = {
        name        = each.value.name
        description = each.value.description
      }
    }
    

    Running terraform plan:

    null_resource.name["bar"]: Refreshing state... [id=5385743772651277751]
    null_resource.name["baz"]: Refreshing state... [id=7923927469678641556]
    null_resource.name["foo"]: Refreshing state... [id=4638368496288058122]
    
    No changes. Your infrastructure matches the configuration.
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search