aztfmod / terraform-azurerm-caf

Terraform supermodule for the Terraform platform engineering for Azure
https://aztfmod.github.io/documentation/
MIT License
562 stars 705 forks source link

Bug report - peering demanding "vnet_key" attribute, when "id" is provided #1795

Open sjackson0109 opened 1 year ago

sjackson0109 commented 1 year ago

VNET peering fails when specifying to = { id = "/subscriptions/xxxxxxxxxxxxx/resourceGroups/vnet/providers/Microsoft.Network/virtualNetworks/vnet1 } for a vnet_peering object.

Is there an existing issue for this?

Community Note

Versions

terraform -v Terraform v1.5.5 on windows_amd64

Affected Resource(s)/Data Source(s)

azurerm_virtual_network_peering

Terraform Configuration Files

vnet_peerings = {
    ##  <<<<<<    HUB TO HUB - BUILDS SUCCESSFULLY   >>>>>>
    # HUB TO HUB - requires to 'allow all vnet traffic' and 'allow forwarded traffic' only
    UKS_HUB_TO_UKW_HUB = {
      name                         = "uks-hub-to-ukw-hub"
      resource_group_key           = "UKS_HUB"
      from = { vnet_key            = "UKS_HUB" }
      to = { vnet_key              = "UKW_HUB" }
      allow_virtual_network_access = true
      allow_forwarded_traffic      = true
      allow_gateway_transit        = false
      use_remote_gateways          = false
    }
    UKW_HUB_TO_UKS_HUB = {
      name                         = "ukw-hub-to-uks-hub"
      resource_group_key           = "UKW_HUB"
      from = { vnet_key            = "UKW_HUB" }
      to = { vnet_key              = "UKS_HUB" }
      allow_virtual_network_access = true
      allow_forwarded_traffic      = true
      allow_gateway_transit        = false
      use_remote_gateways          = false
    }

    ##  <<<<<<    HUB TO SPOKE - FAILS TO BUILD   >>>>>>
## HUB TO SPOKES - allow all vnet traffic and allow advertisement of the gateway connections (transit traffic back-on-prem)
    # UK South
    UKS_HUB_TO_UKS_MGT = {
      direction                    = "hub2spoke"
      name                         = "uks-hub-to-uks-mgt"
      resource_group_key           = "UKS_HUB"
      from = { vnet_key            = "UKW_HUB" }
      to = { id                    = "/subscriptions/REDACTED/resourceGroups/vnet/providers/Microsoft.Network/virtualNetworks/test-vnet-uks-mgt" }
      allow_virtual_network_access = true
      allow_forwarded_traffic      = false
      allow_gateway_transit        = true
      use_remote_gateways          = false
    }
}

Expected Behaviour

As the id attribute was provided inside the to map, then the value provided should have been used to populate the vnet_id of the azurerm_virtual_network_peering resource. Following the example 103 here, line 445-509 describe using either id or vnet_key and lz_key.

The example provided includes:

  # hub_re1_TO_test = {
  #   name = "hub_re1_TO_test"
  #   from = {
  #     vnet_key = "hub_re1"
  #   }
  #   to = {
  #     id = "/subscriptions/xxxxxxxxxxxxx/resourceGroups/vnet/providers/Microsoft.Network/virtualNetworks/vnet1"
  #   }
  #   allow_virtual_network_access = true
  #   allow_forwarded_traffic      = false
  #   allow_gateway_transit        = false
  #   use_remote_gateways          = false
  # }

Matching my use case exactly!

Actual Behaviour

│ Error: Unsupported attribute │ │ on .terraform\modules\aztfmod_hub\networking.tf line 215, in resource "azurerm_virtual_network_peering" "peering": │ 215: remote_virtual_network_id = can(each.value.to.remote_virtual_network_id) ? each.value.to.remote_virtual_network_id : local.combined_objects_networking[try(each.value.to.lz_key, local.client_config.landingzone_key)][each.value.to.vnet_key].id │ ├──────────────── │ │ each.value.to is object with 1 attribute "id" │ │ This object does not have an attribute named "vnet_key".

Steps to Reproduce

terraform init terraform apply -auto-approve

Important Factoids

No response

References

No response

sjackson0109 commented 1 year ago

networking.tf shows

resource "azurecaf_name" "peering" {
  for_each = local.networking.vnet_peerings

  name          = try(each.value.name, "")
  resource_type = "azurerm_virtual_network_peering"
  prefixes      = local.global_settings.prefixes
  random_length = local.global_settings.random_length
  clean_input   = true
  passthrough   = local.global_settings.passthrough
  use_slug      = local.global_settings.use_slug
}

# The code tries to peer to a vnet created in the same landing zone. If it fails it tries with the data remote state
resource "azurerm_virtual_network_peering" "peering" {
  depends_on = [module.networking]
  for_each   = local.networking.vnet_peerings

  name                         = azurecaf_name.peering[each.key].result
  virtual_network_name         = can(each.value.from.virtual_network_name) ? each.value.from.virtual_network_name : local.combined_objects_networking[try(each.value.from.lz_key, local.client_config.landingzone_key)][each.value.from.vnet_key].name
  resource_group_name          = can(each.value.from.resource_group_name) ? each.value.from.resource_group_name : local.combined_objects_networking[try(each.value.from.lz_key, local.client_config.landingzone_key)][each.value.from.vnet_key].resource_group_name
  remote_virtual_network_id    = can(each.value.to.remote_virtual_network_id) ? each.value.to.remote_virtual_network_id : local.combined_objects_networking[try(each.value.to.lz_key, local.client_config.landingzone_key)][each.value.to.vnet_key].id
  allow_virtual_network_access = try(each.value.allow_virtual_network_access, true)
  allow_forwarded_traffic      = try(each.value.allow_forwarded_traffic, false)
  allow_gateway_transit        = try(each.value.allow_gateway_transit, false)
  use_remote_gateways          = try(each.value.use_remote_gateways, false)

}

resource "azapi_resource" "virtualNetworkPeerings" {
  depends_on = [module.networking]
  for_each   = local.networking.vnet_peerings_v1

  type      = "Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2021-05-01"
  name      = each.value.name
  parent_id = can(each.value.from.id) ? each.value.from.id : local.combined_objects_networking[try(each.value.from.lz_key, local.client_config.landingzone_key)][each.value.from.vnet_key].id

  body = jsonencode({
    properties = {
      allowForwardedTraffic     = try(each.value.allow_forwarded_traffic, false)
      allowGatewayTransit       = try(each.value.allow_gateway_transit, false)
      allowVirtualNetworkAccess = try(each.value.allow_virtual_network_access, true)
      doNotVerifyRemoteGateways = try(each.value.do_not_verify_remote_gateways, false)
      useRemoteGateways         = try(each.value.use_remote_gateways, false)
      remoteVirtualNetwork = {
        id = can(each.value.to.remote_virtual_network_id) || can(each.value.to.id) ? try(each.value.to.remote_virtual_network_id, each.value.to.id) : local.combined_objects_networking[try(each.value.to.lz_key, local.client_config.landingzone_key)][each.value.to.vnet_key].id
      }
    }
  })

}

Therefore the command we are investigating is:

  remote_virtual_network_id    = can(each.value.to.remote_virtual_network_id) ? each.value.to.remote_virtual_network_id : local.combined_objects_networking[try(each.value.to.lz_key, local.client_config.landingzone_key)][each.value.to.vnet_key].id

or

        id = can(each.value.to.remote_virtual_network_id) || can(each.value.to.id) ? try(each.value.to.remote_virtual_network_id, each.value.to.id) : local.combined_objects_networking[try(each.value.to.lz_key, local.client_config.landingzone_key)][each.value.to.vnet_key].id

Clearly the JSON side processes either id or remote_virtual_network_id, so the resource should also process the same.