hashicorp / terraform-provider-azurerm

Terraform provider for Azure Resource Manager
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
Mozilla Public License 2.0
4.53k stars 4.61k forks source link

terraform azure app gateway force recreate the child objects which are managed by dynamic blocks #22013

Open raakesh593812 opened 1 year ago

raakesh593812 commented 1 year ago

Is there an existing issue for this?

Community Note

Terraform Version

1.3.2

AzureRM Provider Version

3.29.0

Affected Resource(s)/Data Source(s)

azurerm_application_gateway

Terraform Configuration Files

resource "azurerm_application_gateway" "app_gateway" {
  for_each = local.application_gateways

  name                = each.key
  resource_group_name = data.azurerm_resource_group.resource_group.name
  location            = data.azurerm_resource_group.resource_group.location
  enable_http2        = each.value.enable_http2
  zones               = each.value.zones

  sku {
    name     = each.value.sku.name
    tier     = each.value.sku.tier
    capacity = try(each.value.sku.capacity, null)
  }
  gateway_ip_configuration {
    name      = "${each.key}-gic"
    subnet_id = data.azurerm_subnet.lookups[each.value.subnet].id
  }
  dynamic "frontend_ip_configuration" {
    for_each = each.value.frontend_ip_configurations
    content {
      name                          = frontend_ip_configuration.value.name
      private_ip_address            = frontend_ip_configuration.value.private_ip_address
      public_ip_address_id          = frontend_ip_configuration.value.public_ip_address_id
      private_ip_address_allocation = frontend_ip_configuration.value.private_ip_address_allocation
      subnet_id                     = frontend_ip_configuration.value.subnet_id
    }
  }
  dynamic "frontend_port" {
    for_each = each.value.frontend_ports
    content {
      name = frontend_port.value.name
      port = frontend_port.value.port
    }
  }
  dynamic "backend_address_pool" {
    for_each = each.value.backend_address_pools
    content {
      name         = backend_address_pool.value.name
      fqdns        = backend_address_pool.value.fqdns
      ip_addresses = backend_address_pool.value.ip_addresses
    }
  }
  dynamic "backend_http_settings" {
    for_each = each.value.backend_http_settings
    content {
      name                                = backend_http_settings.value.name
      cookie_based_affinity               = backend_http_settings.value.cookie_based_affinity
      affinity_cookie_name                = backend_http_settings.value.affinity_cookie_name
      port                                = backend_http_settings.value.port
      protocol                            = backend_http_settings.value.protocol
      pick_host_name_from_backend_address = backend_http_settings.value.pick_host_name_from_backend_address
      host_name                           = backend_http_settings.value.host_name
      request_timeout                     = backend_http_settings.value.request_timeout
      probe_name                          = backend_http_settings.value.probe_name
      trusted_root_certificate_names      = backend_http_settings.value.trusted_root_certificate_names
    }
  }
  dynamic "http_listener" {
    for_each = each.value.http_listeners
    content {
      name                           = http_listener.value.name
      frontend_ip_configuration_name = http_listener.value.frontend_ip_configuration_name
      frontend_port_name             = http_listener.value.frontend_port_name
      protocol                       = http_listener.value.protocol
      host_name                      = http_listener.value.host_name
      host_names                     = http_listener.value.host_names
      require_sni                    = http_listener.value.require_sni
      ssl_certificate_name           = http_listener.value.ssl_certificate_name
    }
  }

  dynamic "request_routing_rule" {
    for_each = each.value.request_routing_rules
    content {
      name                       = request_routing_rule.value.name
      rule_type                  = request_routing_rule.value.rule_type
      http_listener_name         = request_routing_rule.value.http_listener_name
      backend_http_settings_name = request_routing_rule.value.backend_http_settings_name
      backend_address_pool_name  = request_routing_rule.value.backend_address_pool_name
      priority                    = request_routing_rule.value.priority
    }
  }

  firewall_policy_id = each.value.firewall_policy_id
  tags               = each.value.tags
}

Debug Output/Panic Output

- http_listener {
          - frontend_ip_configuration_id   = "/subscriptions/23691e30-29e0-45f0-9efa-9e38d2d8358e/resourceGroups/rg-shared-westeurope-gw/providers/Microsoft.Network/applicationGateways/appgatewaysc38/frontendIPConfigurations/appGwFrontendIp1" -> null
          - frontend_ip_configuration_name = "appGwFrontendIp1" -> null
          - frontend_port_id               = "/subscriptions/23691e30-29e0-45f0-9efa-9e38d2d8358e/resourceGroups/rg-shared-westeurope-gw/providers/Microsoft.Network/applicationGateways/appgatewaysc38/frontendPorts/port_80" -> null
          - frontend_port_name             = "port_80" -> null
          - host_name                      = "sapd.cmpny.com" -> null
          - host_names                     = [] -> null
          - id                             = "/subscriptions/23691e30-29e0-45f0-9efa-9e38d2d8358e/resourceGroups/rg-shared-westeurope-gw/providers/Microsoft.Network/applicationGateways/appgatewaysc38/httpListeners/httplistner1" -> null
          - name                           = "httplistner1" -> null
          - protocol                       = "Http" -> null
          - require_sni                    = false -> null
        }
      + http_listener {
          + frontend_ip_configuration_id   = (known after apply)
          + frontend_ip_configuration_name = "appGwFrontendIp1"
          + frontend_port_id               = (known after apply)
          + frontend_port_name             = "port_8080"
          + host_name                      = "sapd2.cmpny.com"
          + host_names                     = []
          + id                             = (known after apply)
          + name                           = "httplistner2"
          + protocol                       = "Http"
          + require_sni                    = false
          + ssl_certificate_id             = (known after apply)
          + ssl_profile_id                 = (known after apply)
        }
      + http_listener {
          + frontend_ip_configuration_id   = "/subscriptions/23691e30-29e0-45f0-9efa-9e38d2d8358e/resourceGroups/rg-shared-westeurope-gw/providers/Microsoft.Network/applicationGateways/appgatewaysc38/frontendIPConfigurations/appGwFrontendIp1"
          + frontend_ip_configuration_name = "appGwFrontendIp1"
          + frontend_port_id               = "/subscriptions/23691e30-29e0-45f0-9efa-9e38d2d8358e/resourceGroups/rg-shared-westeurope-gw/providers/Microsoft.Network/applicationGateways/appgatewaysc38/frontendPorts/port_80"
          + frontend_port_name             = "port_80"
          + host_name                      = "sapd.cmpny.com"
          + host_names                     = []
          + id                             = "/subscriptions/23691e30-29e0-45f0-9efa-9e38d2d8358e/resourceGroups/rg-shared-westeurope-gw/providers/Microsoft.Network/applicationGateways/appgatewaysc38/httpListeners/httplistner1"
          + name                           = "httplistner1"
          + protocol                       = "Http"
          + require_sni                    = false
        }

        # (17 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Expected Behaviour

ideally the child resource http_listener (name: httplistner1) was already created and when i try to add new listener , terraform should create only new listener which was added in input file

Actual Behaviour

ideally the child resource http_listener (name: httplistner1) was already created and when i try to add new listener , terraform force recreating the child resource http_listener (name: httplistner1).

Steps to Reproduce

No response

Important Factoids

No response

References

As per GitHub issue https://github.com/hashicorp/terraform-provider-azurerm/issues/6896 , its was fixed in PR https://github.com/hashicorp/terraform-provider-azurerm/pull/15800.

I still see it in azurerm 3.29.0 and 3.58.0 (latest)

aristosvo commented 1 year ago

Hi @raakesh593812! Thanks for filing this issue.

I am aware this is confusing and know this is not something you'd like to see. Fortunately, the call towards Azure APIs would contain the same information as if there was only a HTTP listener added, as it is just one call with both listeners in the body. I'm unaware that the order of HTTP listeners is influencing the (re)creation of a listener.

Do you see downtime related to this issue?

raakesh593812 commented 1 year ago

yes its causing downtime for those application onboarded on this appgw. Along with http_listener below child resources also has same behavior. Do you know the workaround ? backend_http_settings http_listener request_routing_rule ssl_certificate

aristosvo commented 1 year ago

Do you know the workaround ?

A possible solution could be to make sure the oldest/existing child resources are first in the list, but I'm not sure if that influences anything. There is no other option I could think of.

TBH, I think this issue is a duplicate of #16136, and behaviour will therefore probably not change until v4.0.0 is in the picture.