Asked 1 month ago by VenusianHunter007
Why Does Terraform Recreate My Azure Application Gateway When Adding a New Rule?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by VenusianHunter007
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I've built a Terraform module to manage an Azure Application Gateway. Initially, the resource is created as defined in variable.tf and main.tf. However, when I add a new rule block to variable.tf, Terraform destroys and recreates the app gateway instead of just adding the new rule.
Below is the setup in variable.tf for one rule:
HCL###### RULE apgw-01 ###### variable "rule_apgw-01" { type = map(object({ backend_address_pool_name = string # Name of the backend address pool http_setting_name = string # Name of the backend settings listener_name = string # Name of the listener request_routing_rule_name = string # Name of the rules frontend_port_name = string # Name of the frontend port frontend_port = number priority = number port = number rule_type = string cookie_based_affinity = string protocol = string host_name = string path = string fqdns = string })) default = { "dv-apse-01" = { ### backend_address_pool backend_address_pool_name = "dv-apse-01" fqdns = "dv-apse-01.azurewebsites.net" ### backend_http_settings http_setting_name = "dv-apse-01.settings.http" protocol = "Http" port = 80 cookie_based_affinity = "Disabled" path = "" ### listener listener_name = "dv-apse-01.listener.http" host_name = "test.xyz.it" ### request_routing_rule request_routing_rule_name = "dv-apse-01.rule.http" rule_type = "Basic" frontend_port_name = "port_80" frontend_port = 80 priority = 10 } } }
The main.tf configuration is as follows:
HCLterraform { backend "azurerm" {} } module "apgw-01" { source = "../../../../../modules/application_gateway" for_each = var.rule_apgw-01 rg_name = data.terraform_remote_state.rg.outputs.rg-apgw name = "dv-apgw-01" skuName = "Standard_v2" skuTier = "Standard_v2" private_ip_address = "xx.xx.xx.xx" public_ip_address_id = data.terraform_remote_state.pbip.outputs.public_ip_pbip_apgw_01 tag_division = var.tag_division tag_project = var.tag_project ### backend_address_pool backend_address_pool_name = each.value.backend_address_pool_name fqdns = each.value.fqdns ### backend_http_settings http_setting_name = each.value.http_setting_name protocol = each.value.protocol port = each.value.port cookie_based_affinity = each.value.cookie_based_affinity path = each.value.path ### listener listener_name = each.value.listener_name host_name = each.value.host_name ### request_routing_rule request_routing_rule_name = each.value.request_routing_rule_name rule_type = each.value.rule_type frontend_port_name = each.value.frontend_port_name frontend_port = each.value.frontend_port priority = each.value.priority }
The module is called with:
HCLresource "azurerm_application_gateway" "network" { name = var.name resource_group_name = var.rg_name location = var.location zones = ["1", "2", "3"] sku { name = var.skuName tier = var.skuTier capacity = local.capacity_apgw[substr(var.name, 0, 2)] } gateway_ip_configuration { name = "appGatewayIpConfig" subnet_id = "${local.subnet_id_apgw[substr(var.name, 0, 2)]}" } frontend_port { name = var.frontend_port_name port = var.frontend_port } frontend_ip_configuration { ### public frontend ip name = "appGwPublicFrontendIp" public_ip_address_id = var.public_ip_address_id } frontend_ip_configuration { ### private frontend ip name = "appGwPrivateFrontendIp" subnet_id = "${local.subnet_id_apgw[substr(var.name, 0, 2)]}" private_ip_address_allocation = "Static" private_ip_address = var.private_ip_address } backend_address_pool { name = var.backend_address_pool_name fqdns = [var.fqdns] } backend_http_settings { name = var.http_setting_name cookie_based_affinity = var.cookie_based_affinity path = var.path port = var.port protocol = var.protocol request_timeout = var.request_timeout pick_host_name_from_backend_address = var.pick_host_name_from_backend_address probe_name = "dv-apse-we-01.probe.http" } http_listener { name = var.listener_name frontend_ip_configuration_name = "appGwPrivateFrontendIp" frontend_port_name = var.frontend_port_name protocol = var.protocol host_name = var.host_name } request_routing_rule { name = var.request_routing_rule_name priority = var.priority rule_type = var.rule_type http_listener_name = var.listener_name backend_address_pool_name = var.backend_address_pool_name backend_http_settings_name = var.http_setting_name } probe { name = "dv-apse-we-01.probe.http" protocol = "Http" pick_host_name_from_backend_http_settings = true path = "/" interval = 30 timeout = 30 unhealthy_threshold = 3 match { status_code = ["200-399"] } } probe { name = "dv-apse-we-01.probe.https" protocol = "Https" pick_host_name_from_backend_http_settings = true path = "/" interval = 30 timeout = 30 unhealthy_threshold = 3 match { status_code = ["200-399"] } } tags = { division = var.tag_division project = var.tag_project resourceType = var.tag_resourceType referent = var.tag_referent } lifecycle { ignore_changes = [tags] } }
The issue arises when I add a new block to variable.tf, for example:
HCLdefault = { "app01" = { ### backend_address_pool backend_address_pool_name = "app01" fqdns = "app01.azurewebsites.net" ### backend_http_settings http_setting_name = "app01.settings.http" protocol = "Http" port = 80 cookie_based_affinity = "Disabled" path = "" ### listener listener_name = "app01.listener.http" host_name = "test.xyz.it" ### request_routing_rule request_routing_rule_name = "app01.rule.http" rule_type = "Basic" frontend_port_name = "port_80" frontend_port = 80 priority = 10 } "app02" = { ### backend_address_pool backend_address_pool_name = "app02" fqdns = "app02.azurewebsites.net" ### backend_http_settings http_setting_name = "app02.settings.http" protocol = "Http" port = 80 cookie_based_affinity = "Disabled" path = "" ### listener listener_name = "app02.listener.http" host_name = "test2.xyz.it" ### request_routing_rule request_routing_rule_name = "app02.rule.http" rule_type = "Basic" frontend_port_name = "port_80" frontend_port = 80 priority = 10 } } }
Upon running terraform plan, it appears that Terraform selects the new block (e.g. "app02") and then attempts to destroy the existing app gateway (named "dv-apgw-01") and recreate it, even though the gateway already exists. The plan output includes an error like:
module.apgw-apse-01["dv-apse-02"].azurerm_application_gateway.network: Creating...
Error: A resource with the ID"dv-apgw-01" already exists - to be managed via Terraform this resource needs to be imported into the State...
Why doesn't Terraform recognize that the application gateway already exists and simply add the new rules?
Any help or guidance would be appreciated.
Comments 0
•Answers 3
•Views 61
Answer by TychoAI Agent • 1 month ago
The issue arises because you’re instantiating multiple module instances (using for_each) that all create an application gateway with the same hard‐coded name ("dv-apgw-01"). When you add new rules (new map entries), Terraform creates another module instance that tries to create an app gateway with the same name, resulting in a conflict with the resource already managed in state.
Refactor the Module:
Parameterize the Gateway Name:
If your intention is to manage all rules on one app gateway, consider something like:
HCLresource "azurerm_application_gateway" "network" { name = var.name # e.g., "dv-apgw-01" resource_group_name = var.rg_name location = var.location zones = ["1", "2", "3"] sku { name = var.skuName tier = var.skuTier capacity = local.capacity_apgw[substr(var.name, 0, 2)] } gateway_ip_configuration { name = "appGatewayIpConfig" subnet_id = local.subnet_id_apgw[substr(var.name, 0, 2)] } frontend_port { for_each = var.rule_apgw-01 name = each.value.frontend_port_name port = each.value.frontend_port } frontend_ip_configuration { name = "appGwPublicFrontendIp" public_ip_address_id = var.public_ip_address_id } frontend_ip_configuration { name = "appGwPrivateFrontendIp" subnet_id = local.subnet_id_apgw[substr(var.name, 0, 2)] private_ip_address_allocation = "Static" private_ip_address = var.private_ip_address } backend_address_pool { for_each = var.rule_apgw-01 name = each.value.backend_address_pool_name fqdns = [each.value.fqdns] } backend_http_settings { for_each = var.rule_apgw-01 name = each.value.http_setting_name port = each.value.port protocol = each.value.protocol cookie_based_affinity = each.value.cookie_based_affinity path = each.value.path request_timeout = var.request_timeout pick_host_name_from_backend_address = var.pick_host_name_from_backend_address probe_name = "dv-apse-we-01.probe.http" } http_listener { for_each = var.rule_apgw-01 name = each.value.listener_name frontend_ip_configuration_name = "appGwPrivateFrontendIp" frontend_port_name = each.value.frontend_port_name protocol = each.value.protocol host_name = each.value.host_name } request_routing_rule { for_each = var.rule_apgw-01 name = each.value.request_routing_rule_name priority = each.value.priority rule_type = each.value.rule_type http_listener_name = each.value.listener_name backend_address_pool_name = each.value.backend_address_pool_name backend_http_settings_name = each.value.http_setting_name } # Probes and tags as required ... tags = { division = var.tag_division project = var.tag_project resourceType = var.tag_resourceType referent = var.tag_referent } lifecycle { ignore_changes = [tags] } }
In the above example, one gateway is created, and its configuration dynamically iterates over your rules. This removes the conflict of trying to create multiple gateways with the same name.
Terraform isn’t “forgetting” the existing gateway—it’s that your module design, when using for_each, creates multiple full gateway resources that clash because they share the same static name. Refactoring your module to create a single gateway with multiple dynamic rule blocks or ensuring a unique gateway name per instance will resolve the conflict.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by VoidKeeper487 • 1 month ago
The issue here is that the variable name
is assigned a static value of name = "dv-apgw-01"
, hence the Azure provider is complaining about the resource with the same name already existing. The easiest would be to assign it a value dynamically, e.g, name = each.key
or something similar. However, if you want to have the same API gateway with multiple resources, then the child module (i.e., "../../../../../modules/application_gateway"
) has to use for_each
and not the root module.
No comments yet.
Answer by AstroEngineer707 • 1 month ago
as written before, i solved using dynamic block:
HCLvariable "backend_pools" { description = "Map of backend pools for the application gateway" type = list(object({ backend_address_pool_name = string # Name of the backend pool http_setting_name = string # Name of the backend settings listener_name = string # Name of the listener request_routing_rule_name = string # Name of the rules frontend_port_name = string # Name of the frontend port frontend_port = number priority = number port = number rule_type = string cookie_based_affinity = string protocol = string host_name = string path = string fqdns = string })) default = [ { ### backend_address_pool backend_address_pool_name = "dv-apse-01" fqdns = "dv-apse-01.azurewebsites.net" ### backend_http_settings http_setting_name = "dv-apse-01.settings.http" protocol = "Http" port = 80 cookie_based_affinity = "Disabled" path = "" ### listener listener_name = "dv-apse-01.listener.http" host_name = "app.aaa.it" ### request_routing_rule request_routing_rule_name = "dv-apse-01.rule.http" rule_type = "Basic" frontend_port_name = "port_80" frontend_port = 80 priority = 10 }, { ### backend_address_pool backend_address_pool_name = "dv-apse-02" fqdns = "dv-apse-02.azurewebsites.net" ### backend_http_settings http_setting_name = "dv-apse-02.settings.http" protocol = "Http" port = 80 cookie_based_affinity = "Disabled" path = "" ### listener listener_name = "dv-apse-02.listener.http" host_name = "aaaa.aaa.it" ### request_routing_rule request_routing_rule_name = "dv-apse-02.rule.http" rule_type = "Basic" frontend_port_name = "port_80" frontend_port = 80 priority = 20 }, { ### backend_address_pool backend_address_pool_name = "dv-apse-03" fqdns = "dv-apse-03.azurewebsites.net" ### backend_http_settings http_setting_name = "dv-apse-03.settings.http" protocol = "Http" port = 80 cookie_based_affinity = "Disabled" path = "" ### listener listener_name = "dv-apse-03.listener.http" host_name = "asdad.paa.it" ### request_routing_rule request_routing_rule_name = "dv-apse-03.rule.http" rule_type = "Basic" frontend_port_name = "port_80" frontend_port = 80 priority = 30 } ] }
and main now is like:
HCLresource "azurerm_application_gateway" "network" { name = var.name resource_group_name = var.rg_name location = var.location zones = ["1", "2", "3"] sku { name = var.skuName tier = var.skuTier capacity = local.capacity_apgw[substr(var.name, 0, 2)] } gateway_ip_configuration { name = "appGatewayIpConfig" subnet_id = "${local.subnet_id_apgw[substr(var.name, 0, 2)]}" } frontend_ip_configuration { ### public frontend ip name = "appGwPublicFrontendIp" public_ip_address_id = var.public_ip_address_id } frontend_ip_configuration { ### private frontend ip name = "appGwPrivateFrontendIp" subnet_id = "${local.subnet_id_apgw[substr(var.name, 0, 2)]}" private_ip_address_allocation = "Static" private_ip_address = var.private_ip_address } dynamic frontend_port { for_each = var.backend_pools content { name = frontend_port.value.frontend_port_name port = frontend_port.value.frontend_port } } dynamic backend_address_pool { for_each = var.backend_pools content { name = backend_address_pool.value.backend_address_pool_name fqdns = [backend_address_pool.value.fqdns] } } dynamic backend_http_settings { for_each = var.backend_pools content { name = backend_http_settings.value.http_setting_name cookie_based_affinity = backend_http_settings.value.cookie_based_affinity path = backend_http_settings.value.path port = backend_http_settings.value.port protocol = backend_http_settings.value.protocol request_timeout = var.request_timeout pick_host_name_from_backend_address = var.pick_host_name_from_backend_address probe_name = "dv-apse-we-01.probe.http" } } dynamic http_listener { for_each = var.backend_pools content { name = http_listener.value.listener_name frontend_ip_configuration_name = "appGwPrivateFrontendIp" frontend_port_name = http_listener.value.frontend_port_name protocol = http_listener.value.protocol host_name = http_listener.value.host_name } } dynamic request_routing_rule { for_each = var.backend_pools content { name = request_routing_rule.value.request_routing_rule_name priority = request_routing_rule.value.priority rule_type = request_routing_rule.value.rule_type http_listener_name = request_routing_rule.value.listener_name backend_address_pool_name = request_routing_rule.value.backend_address_pool_name backend_http_settings_name = request_routing_rule.value.http_setting_name } } probe { name = "dv-apse-we-01.probe.http" protocol = "Http" pick_host_name_from_backend_http_settings = true path = "/" interval = 30 timeout = 30 unhealthy_threshold = 3 match { status_code = ["200-399"] } } probe { name = "dv-apse-we-01.probe.https" protocol = "Https" pick_host_name_from_backend_http_settings = true path = "/" interval = 30 timeout = 30 unhealthy_threshold = 3 match { status_code = ["200-399"] } } tags = { division = var.tag_division project = var.tag_project resourceType = var.tag_resourceType referent = var.tag_referent } lifecycle { ignore_changes = [tags] } }
No comments yet.
No comments yet.