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.59k stars 4.63k forks source link

azurerm_network_interface_application_gateway_backend_address_pool_association backend_address_pool addressable key #16855

Open johnastiles opened 2 years ago

johnastiles commented 2 years ago

Is there an existing issue for this?

Community Note

Terraform Version

1.1.9

AzureRM Provider Version

3.6.0

Affected Resource(s)/Data Source(s)

azurerm_network_interface_application_gateway_backend_address_pool_association

Terraform Configuration Files

resource "azurerm_resource_group" "example" {
  name     = "example-resources"
  location = "West Europe"
}

resource "azurerm_virtual_network" "example" {
  name                = "example-network"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
}

resource "azurerm_subnet" "frontend" {
  name                 = "frontend"
  resource_group_name  = azurerm_resource_group.example.name
  virtual_network_name = azurerm_virtual_network.example.name
  address_prefixes     = ["10.0.1.0/24"]
}

resource "azurerm_subnet" "backend" {
  name                 = "backend"
  resource_group_name  = azurerm_resource_group.example.name
  virtual_network_name = azurerm_virtual_network.example.name
  address_prefixes     = ["10.0.2.0/24"]
}

resource "azurerm_public_ip" "example" {
  name                = "example-pip"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  allocation_method   = "Dynamic"
}

# since these variables are re-used - a locals block makes this more maintainable
locals {
  backend_address_pool_name      = "${azurerm_virtual_network.example.name}-beap"
  frontend_port_name             = "${azurerm_virtual_network.example.name}-feport"
  frontend_ip_configuration_name = "${azurerm_virtual_network.example.name}-feip"
  http_setting_name              = "${azurerm_virtual_network.example.name}-be-htst"
  listener_name                  = "${azurerm_virtual_network.example.name}-httplstn"
  request_routing_rule_name      = "${azurerm_virtual_network.example.name}-rqrt"
}

resource "azurerm_application_gateway" "network" {
  name                = "example-appgateway"
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location

  sku {
    name     = "Standard_Small"
    tier     = "Standard"
    capacity = 2
  }

  gateway_ip_configuration {
    name      = "my-gateway-ip-configuration"
    subnet_id = azurerm_subnet.frontend.id
  }

  frontend_port {
    name = local.frontend_port_name
    port = 80
  }

  frontend_ip_configuration {
    name                 = local.frontend_ip_configuration_name
    public_ip_address_id = azurerm_public_ip.example.id
  }

  backend_address_pool {
    name = local.backend_address_pool_name
  }

  backend_http_settings {
    name                  = local.http_setting_name
    cookie_based_affinity = "Disabled"
    port                  = 80
    protocol              = "Http"
    request_timeout       = 1
  }

  http_listener {
    name                           = local.listener_name
    frontend_ip_configuration_name = local.frontend_ip_configuration_name
    frontend_port_name             = local.frontend_port_name
    protocol                       = "Http"
  }

  request_routing_rule {
    name                       = local.request_routing_rule_name
    rule_type                  = "Basic"
    http_listener_name         = local.listener_name
    backend_address_pool_name  = local.backend_address_pool_name
    backend_http_settings_name = local.http_setting_name
  }
}

resource "azurerm_network_interface" "example" {
  name                = "example-nic"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name

  ip_configuration {
    name                          = "testconfiguration1"
    subnet_id                     = azurerm_subnet.frontend.id
    private_ip_address_allocation = "Dynamic"
  }
}

resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "example" {
  network_interface_id    = azurerm_network_interface.example.id
  ip_configuration_name   = "testconfiguration1"
  backend_address_pool_id = azurerm_application_gateway.network.backend_address_pool[0].id
}

Debug Output/Panic Output

│ Error: Cannot index a set value
│
│   on appgateway.tf line 185, in resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "vm":
  185:   backend_address_pool_id = azurerm_application_gateway.network.backend_address_pool[0].id
│
│ Block type "backend_address_pool" is represented by a set of objects, and set elements do not have addressable keys. To find elements matching specific criteria, use a "for" expression with an "if" clause.

Expected Behaviour

Application Gateway backend pool association should be made with the virtual machine nic.

Actual Behaviour

States that backend_address_pool does not have addressable keys.

Steps to Reproduce

Terraform plan

Important Factoids

This worked as expected in Azure Provider 2.99.0

References

https://github.com/MicrosoftDocs/azure-dev-docs/issues/770 https://github.com/MicrosoftDocs/azure-dev-docs/issues/752

Iduoad commented 2 years ago

+1

I have the same problem here!

Loth4r commented 2 years ago

Managed to bypass this issue by below 'hack' that is exploiting the fact that backend_address_pool id (as many other azure resources) is constructed/consists backend's pool name:

resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "example" {
  network_interface_id    = azurerm_network_interface.example.id
  ip_configuration_name   = "testconfiguration1"
  backend_address_pool_id = [for value in tolist(azurerm_application_gateway.network.backend_address_pool.*.id) : value if length(regexall(lower(local.backend_address_pool_name), value)) > 0][0]
}

Above code uses conditional for expression that iterates through list of backend_addres_pool id's (created with usage of splat operator) and assigns value of the only element that contains backend_addres_pool_name (used lenght(regexall(...)) to check that)

Not ideal, not clean, but working for me...

kutscherma commented 2 years ago

Same here.

The solution above works but it takes a long time to create.

@Loth4r do you have the same problem?

Loth4r commented 2 years ago

Same here.

The solution above works but it takes a long time to create.

@Loth4r do you have the same problem?

No such issue on my end.

stimms commented 2 years ago

Replicated the Cannot index a set value issue both 3.3.0 and 3.10.0 of hashicorp/azurerm. Workaround appears to work but I don't know about the speed yet

AokigaharaX commented 2 years ago

In 3.11.0 they updated the documentation as stated below:

resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "example" {
  network_interface_id    = azurerm_network_interface.example.id
  ip_configuration_name   = "testconfiguration1"
  backend_address_pool_id = tolist(azurerm_application_gateway.network.backend_address_pool).0.id
}
Loth4r commented 2 years ago

In 3.11.0 they updated the documentation as stated below:

resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "example" {
  network_interface_id    = azurerm_network_interface.example.id
  ip_configuration_name   = "testconfiguration1"
  backend_address_pool_id = tolist(azurerm_application_gateway.network.backend_address_pool).0.id
}

Above will most likely work, but in scenario where you have only one backend_address_pool defined on your app gateway. If you have more then in my opinion it can lead to some random results as tolist() function on a set does not guarantee order:

Pass a set value to tolist to convert it to a list. Since set elements are not ordered, the resulting list will have an undefined order that will be consistent within a particular run of Terraform.

see (https://www.terraform.io/language/functions/tolist)

What could improve setting up backend_addres_pool association here would be reconsidering how azurerm_application_gateway component should be exporting backend_address_pool block. Currently it is exporting list of backend_address_pool as follows:

A backend_address_pool block exports the following:

id - The ID of the Backend Address Pool.

Maybe changing type to map that has a name of backend address pool as a key would do the trick? Then it could look more like below:

resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "example" {
  network_interface_id    = azurerm_network_interface.example.id
  ip_configuration_name   = "testconfiguration1"
  backend_address_pool_id = azurerm_application_gateway.network.backend_address_pool[<backend_pool_name>].id
}
skinlayers commented 2 years ago

Neither solutions is working for me:

│ Error: Incorrect attribute value type
│
│   on vmss.tf line 58, in resource "azurerm_linux_virtual_machine_scale_set" "vmss":
│   58:       application_gateway_backend_address_pool_ids = [for value in tolist(azurerm_application_gateway.network.backend_address_pool.*.id) : value if length(regexall(lower(local.backend_address_pool_name), value)) > 0][0]
│     ├────────────────
│     │ azurerm_application_gateway.network.backend_address_pool is set of object with 1 element
│     │ local.backend_address_pool_name is "vmss-vnet-beap"
│
│ Inappropriate value for attribute "application_gateway_backend_address_pool_ids": set of string required.
│ Error: Incorrect attribute value type
│
│   on vmss.tf line 58, in resource "azurerm_linux_virtual_machine_scale_set" "vmss":
│   58:       application_gateway_backend_address_pool_ids = tolist(azurerm_application_gateway.network.backend_address_pool).0.id
│     ├────────────────
│     │ azurerm_application_gateway.network.backend_address_pool is set of object with 1 element
│
│ Inappropriate value for attribute "application_gateway_backend_address_pool_ids": set of string required.

However, it works if I use:

      application_gateway_backend_address_pool_ids = azurerm_application_gateway.network.backend_address_pool.*.id
GeoffCraigVRU commented 2 years ago

We ran into the ordering issue with tolist(). In order to get around it we created a local variable. backend_nics was the output of the VM NICs we wanted in or backend pool.

locals { be_to_nic = flatten([for k in tolist(azurerm_application_gateway.app_gateway.backend_address_pool).*.id : [ for l, w in var.backend_nics : { backend_pool_id = k nic_id = w.id nic_ipconfig_name = w.ip_configuration[0].name } ] ]) }

Then in the terraform we did this.

resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "nic_app_be_association" { for_each = tomap({ for k in local.be_to_nic : "${k.backend_pool_id}${k.nic_id}" => k }) network_interface_id = each.value.nic_id ip_configuration_name = each.value.nic_ipconfig_name backend_address_pool_id = each.value.backend_pool_id }

There may be a better way, but this worked for us.

jajera commented 1 year ago

encountered this too. this workaround suggested above worked.

tolist(azurerm_application_gateway.network.backend_address_pool).0.id

devanshujoshi139 commented 1 year ago

You can use one function from terraform as below:

resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "nic-assoc" { backend_address_pool_id = one(azurerm_application_gateway.aggateway.backend_address_pool).id ip_configuration_name = "web-linuxvm-ipconfig" network_interface_id = azurerm_network_interface.web_linuxvm_nic.id }

djryanj commented 7 months ago

@devanshujoshi139 that only works if there's a single backend address pool. As soon as there are more, that will fail.

@GeoffCraigVRU it's probably relevant that the backend address pool ID's are easy to predict. In my case, it was a lot easier to just do this:

resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "nic1" {
  backend_address_pool_id = "${azurerm_application_gateway.this.id}/backendAddressPools/bapName"
  ip_configuration_name   = "ipConfigName"
  network_interface_id    = azurerm_network_interface.vm1nic1.id
}

Ideally, even though the Azure API returns the data in an unordered list (which is why the backend_address_pools attribute is a set) the AzureRM provider would be able to intelligently make it findable by name since it stores it as a hash (apparently) which won't change no matter what order things come out of the API in and it would be easy to parse the name off the end of those identifiers. I'm sure there's a reason they haven't done this, but a little sprinkle of syntactic sugar might be nice.