TFNFR7 - Count & for_each Use

ID: TFNFR7 - Category: Code Style - count & for_each Use

We can use count and for_each to deploy multiple resources, but using count with an ordered collection can create an index anti-pattern where removing one item unexpectedly changes other resource addresses.

You can use count to create some kind of resources under certain conditions, for example:

resource "azapi_resource" "network_security_group" {
  count     = local.create_new_security_group ? 1 : 0
  type      = "Microsoft.Network/networkSecurityGroups@2023-11-01"
  name      = coalesce(var.new_network_security_group_name, "${var.subnet_name}-nsg")
  parent_id = var.parent_id
  location  = local.location
  tags      = var.new_network_security_group_tags
  body = {
    properties = {}
  }
  response_export_values = []
}

The module’s owners MUST use map(xxx) or set(xxx) as resource’s for_each collection, the map’s key or set’s element MUST be static literals.

Good example:

resource "azapi_resource" "subnet_pair" {
  for_each  = var.subnet_map // `map(string)`, when user call this module, it could be: `{ "subnet0": "subnet0" }`, or `{ "subnet0": azapi_resource.subnet0.name }`
  type      = "Microsoft.Network/virtualNetworks/subnets@2023-11-01"
  name      = "${each.value}-pair"
  parent_id = azapi_resource.virtual_network.id
  body = {
    properties = {
      addressPrefixes = ["10.0.1.0/24"]
    }
  }
  response_export_values = []
}

Bad example:

resource "azapi_resource" "subnet_pair" {
  for_each  = var.subnet_name_set // `set(string)`, when user use `toset([azapi_resource.subnet0.name])`, it would cause an error.
  type      = "Microsoft.Network/virtualNetworks/subnets@2023-11-01"
  name      = "${each.value}-pair"
  parent_id = azapi_resource.virtual_network.id
  body = {
    properties = {
      addressPrefixes = ["10.0.1.0/24"]
    }
  }
  response_export_values = []
}