Azure / terraform-azurerm-avm-ptn-vnetgateway

AVM Terraform Pattern Module for Virtual Network Gateway
https://registry.terraform.io/modules/Azure/avm-ptn-vnetgateway/azurerm/latest
MIT License
7 stars 5 forks source link

fails if subnet created in same configuration as module call (azurerm_subnet count depends on subnet_id) #19

Closed AndyAtTib closed 5 months ago

AndyAtTib commented 7 months ago

Is there an existing issue for this?

Greenfield/Brownfield provisioning

greenfield

Terraform Version

1.7.1

Module Version

0.2.1

AzureRM Provider Version

3.89.0

Affected Resource(s)/Data Source(s)

azurerm_subnet.vgw

https://github.com/Azure/terraform-azurerm-avm-ptn-vnetgateway/blob/5932b334dcb6febb4d2fe386ee0d070dc400ba1d/main.tf#L2

Terraform Configuration Files

Click to expand for MRE configuration file

```hcl #region Providers terraform { required_version = "1.7.1" required_providers { azurerm = { source = "hashicorp/azurerm" version = "3.89.0" } local = { source = "hashicorp/local" version = "2.4.1" } random = { source = "hashicorp/random" version = "3.6.0" } tls = { source = "hashicorp/tls" version = "4.0.5" } } } provider "azurerm" { features { resource_group { prevent_deletion_if_contains_resources = false } } skip_provider_registration = true storage_use_azuread = true } provider "local" {} provider "tls" {} #endregion Providers #region Vnet locals { ic00_region = "westeurope" ic00_region_abbrev = "aweu" ic00_cidr = cidrsubnet("10.0.0.0/8", 8, 0) in00_global = { region = local.ic00_region region_abbrev = local.ic00_region_abbrev network_cidr = local.ic00_cidr } in00_naming = { workload = "modtest" region = local.ic00_region_abbrev idx = "000" } in01_snets = [ { name = "snet-000", address_prefix = cidrsubnet(local.ic00_cidr, 8, 0) }, { name = "snet-001", address_prefix = cidrsubnet(local.ic00_cidr, 8, 1) }, { name = "GatewaySubnet", address_prefix = cidrsubnet(local.ic00_cidr, 8, 2) }, ] } module "naming_vnet" { # source = "Azure/naming/azurerm" # version = "0.4.0" # "8a1c8616d4cd05423e53c3260a016919ce0df33d" source = "git::https://github.com/Azure/terraform-azurerm-naming.git?ref=8a1c8616d4cd05423e53c3260a016919ce0df33d" suffix = [ local.in00_naming.workload, "vnet", local.in00_naming.region, local.in00_naming.idx ] } resource "azurerm_resource_group" "vnet" { location = local.in00_global.region name = module.naming_vnet.resource_group.name_unique } resource "azurerm_virtual_network" "main" { name = module.naming_vnet.virtual_network.name_unique resource_group_name = azurerm_resource_group.vnet.name location = local.in00_global.region address_space = [local.in00_global.network_cidr] } resource "azurerm_subnet" "all" { for_each = { for x in local.in01_snets : x.name => x } resource_group_name = azurerm_resource_group.vnet.name virtual_network_name = azurerm_virtual_network.main.name name = each.value.name address_prefixes = [each.value.address_prefix] } data "azurerm_virtual_network" "main" { depends_on = [azurerm_virtual_network.main, azurerm_subnet.all] resource_group_name = azurerm_resource_group.vnet.name name = azurerm_virtual_network.main.name } data "azurerm_subnet" "all" { for_each = { for x in local.in01_snets[*].name : x => x } depends_on = [azurerm_virtual_network.main, azurerm_subnet.all] resource_group_name = azurerm_resource_group.vnet.name virtual_network_name = data.azurerm_virtual_network.main.name name = each.value } #endregion Vnet #region Vpn locals { ic03_vpn_subnet = [ for k, v in azurerm_subnet.all : v if k == "GatewaySubnet" ][0] in03_vpn = { gw_params = { subnet = local.ic03_vpn_subnet apipa_prefix_bits = 0 } } } module "naming_vpn" { # source = "Azure/naming/azurerm" # version = "0.4.0" # "8a1c8616d4cd05423e53c3260a016919ce0df33d" source = "git::https://github.com/Azure/terraform-azurerm-naming.git?ref=8a1c8616d4cd05423e53c3260a016919ce0df33d" suffix = [ local.in00_naming.workload, "vpn", local.in00_naming.region, "000" ] } module "vpn" { # source = "Azure/avm-ptn-vnetgateway/azurerm" # version = "0.2.1" # "5932b334dcb6febb4d2fe386ee0d070dc400ba1d" source = "git::https://github.com/Azure/terraform-azurerm-avm-ptn-vnetgateway.git?ref=5932b334dcb6febb4d2fe386ee0d070dc400ba1d" virtual_network_resource_group_name = data.azurerm_virtual_network.main.resource_group_name location = local.in00_global.region virtual_network_name = data.azurerm_virtual_network.main.name name = module.naming_vpn.virtual_network_gateway.name_unique # TODO: causes failure /* Error: Invalid count argument on .terraform/modules/vpn/main.tf line 2, in resource "azurerm_subnet" "vgw": 2: count = var.subnet_id != "" ? 0 : 1 The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on. */ subnet_id = local.in03_vpn.gw_params.subnet.id type = "Vpn" vpn_generation = "Generation2" sku = "VpnGw2" vpn_private_ip_address_enabled = false vpn_active_active_enabled = true ip_configurations = { for y in [ for x in range(0, 2) : { ip_configuration_name = "ipc-${format("%03d", x)}" private_ip_address_allocation = "Dynamic" apipa_address = cidrsubnet("169.254.2${x + 1}.0/24", 6, local.in03_vpn.gw_params.apipa_prefix_bits) public_ip = { name = "${module.naming_vpn.public_ip.name_unique}--${format("%02d", x)}" allocation_method = "Dynamic" sku = "Basic" tags = null } } ] : y.ip_configuration_name => y } express_route_circuits = {} local_network_gateways = {} vpn_type = "RouteBased" vpn_bgp_enabled = false vpn_bgp_settings = {} route_table_creation_enabled = false vpn_point_to_site = null } #endregion Vpn ```

tfvars variables values

# no variables in sample

Debug Output/Panic Output

...
Plan: 13 to add, 0 to change, 0 to destroy.
╷
│ Error: Invalid count argument
│
│   on .terraform/modules/vpn/main.tf line 2, in resource "azurerm_subnet" "vgw":
│    2:   count = var.subnet_id != "" ? 0 : 1
│
│ The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances
│ will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.
╵

Expected Behaviour

The module works in a configuration with a gateway subnet that is created by the configuration that calls the module.

Actual Behaviour

If a new vnet and subnets are created in the configuration calling this module, when the subnet_id is used as the module argument, then the module fails with the error shown at the plan stage.

Steps to Reproduce

terraform init && terraform plan -out tfplan

Important Factoids

no

References

none

luke-taylor commented 6 months ago

Good callout, I noticed the 1.8-alpha release of Terraform is allowing for unknown values in count. This could be worth trying for now. Alternatively we could implement a dedicated variable managing creation e.g. subnet_creation_enabled.

AndyAtTib commented 6 months ago

It depends on the standard pattern you want to adopt. Is there an AVM standard for this kind of thing? It seems like it must be a common pattern.

I think feature toggles are easy to understand. I think verb_thing feature toggles names like create_subnet, allow_ssh are short and easy to remember. Is there an AVM standard for feature toggle naming, too?

In terms of >= 1.8 vs feature toggle:

With feature toggles, if you need to you could enforce input consistency using resource "terraform_data" with preconditions in it. For example you can throw an error if someone has supplied a subnet_id but also set a toggle create_subnet to true.

resource "terraform_data" "subnet_var_validation" {
  lifecycle {
    precondition {
      condition = (
        var.create_subnet == (try(var.subnet_id, "") == "")
      )
      error_message = <<EOT
        Inconsistent variables: `create_subnet` must be `false` if `subnet_id` is set;
        otherwise `create_subnet` must be `true`"
      EOT
    }
  }
}

resource "subnet" "main" {
...
  depends_on = [terraform_data.subnet_var_validation]
}
luke-taylor commented 6 months ago

There is a standard - here's the link TFNFR16. So in our case it would subnet_creation_enabled.