Terraform: lookup a map set default value as a list

Created on 13 Nov 2019  路  4Comments  路  Source: hashicorp/terraform

Terraform Version

v.0.12.10

Terraform Configuration Files


main.tf

resource "azurerm_network_security_rule" "predefined_rules" {
  count                       = length(var.predefined_rules)
  name                        = lookup(var.predefined_rules[count.index], "name")
  source_address_prefix       = join(",", lookup(var.predefined_rules[count.index], "source_address_prefix", var.source_address_prefix))
  destination_address_prefix  = join(",", lookup(var.predefined_rules[count.index], "destination_address_prefix", var.destination_address_prefix))
  resource_group_name         = azurerm_resource_group.nsg.name
  network_security_group_name = azurerm_network_security_group.nsg.name
}

variable.tf

# Predefined rules   
variable "predefined_rules" {
  type    = list(any)
  default = []
}
# source address prefix to be applied to all rules
variable "source_address_prefix" {
  type    = list(string)
  default = ["*"]

  # Example ["10.0.3.0/24"] or ["VirtualNetwork"]
}

# Destination address prefix to be applied to all rules
variable "destination_address_prefix" {
  type    = list(string)
  default = ["*"]

  # Example ["10.0.3.0/32","10.0.3.128/32"] or ["VirtualNetwork"] 
}

Debug Output

Crash Output

Error: Invalid function argument
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:   on ../../main.tf line 27, in resource "azurerm_network_security_rule" "predefined_rules":
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:   27:   source_address_prefix       = join(",", lookup(var.predefined_rules[count.index], "source_address_prefix", var.source_address_prefix))
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:     |----------------
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:     | var.source_address_prefix is list of string with 1 element
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100: Invalid value for "default" parameter: the default value must have the same
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100: type as the map elements.
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:
Error: Invalid function argument
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:   on ../../main.tf line 28, in resource "azurerm_network_security_rule" "predefined_rules":
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:   28:   destination_address_prefix  = join(",", lookup(var.predefined_rules[count.index], "destination_address_prefix", var.destination_address_prefix))
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:     |----------------
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:     | var.destination_address_prefix is list of string with 1 element
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100:
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100: Invalid value for "default" parameter: the default value must have the same
TestTerraformNSG 2019-11-13T06:42:00Z command.go:100: type as the map elements.

Expected Behavior


I want to lookup the key source_address_prefix in predefined_rules[index] map,it's a list, if the key is not included in the map, I'd like to return a list(string) var.source_address_prefix, which isn't supported

Actual Behavior


error raised

Steps to Reproduce

Additional Context

References

bug config v0.12

Most helpful comment

I've just found another workaround for this that works well for my case (aws alb listener rule actions) - I switched my var type from list(any) to string and have the consumer pass the value through jsonencode, which I then jsondecode in the module (e.g. for_each = jsondecode(var.any_list)) and use try(setting.value.prop, null).

All 4 comments

Hi @yupwei68! Sorry for this strange behavior, and thanks for reporting it.

It looks like the type checking in the lookup function is getting confused by the list(any) type on that variable. It _should_ respond to that situation (that is, when the element type of the list isn't known yet) by marking the result as unknown, but it seems to be detecting an error instead.

We'll investigate this further in the future, but in the meantime we think you should be able to work around it by defining an explicit element type for var.predefined_rules. For example:

variable "predefined_rules" {
  type = list(object({
    name                       = string
    source_address_prefix      = list(string)
    destination_address_prefix = list(string)
  }))
}

That would then lead to a slightly different resource configuration:

resource "azurerm_network_security_rule" "predefined_rules" {
  count = length(var.predefined_rules)

  name                        = var.predefined_rules[count.index].name
  source_address_prefix       = join(",", var.predefined_rules[count.index].source_address_prefix != null ? var.predefined_rules[count.index].source_address_prefix : var.source_address_prefix)
  destination_address_prefix  = join(",", var.predefined_rules[count.index].destination_address_prefix != null ? var.predefined_rules[count.index].destination_address_prefix : var.destination_address_prefix)
  resource_group_name         = azurerm_resource_group.nsg.name
  network_security_group_name = azurerm_network_security_group.nsg.name
}

or, alternatively, using for_each to identify each rule by its name and simplify the expressions further:

resource "azurerm_network_security_rule" "predefined_rules" {
  for_each = { for r in var.predefined_rules : r.name => r }

  name                        = each.key
  source_address_prefix       = join(",", each.value.source_address_prefix != null ? each.value.source_address_prefix : var.source_address_prefix)
  destination_address_prefix  = join(",", each.value.destination_address_prefix != null ? each.value.destination_address_prefix : var.destination_address_prefix)
  resource_group_name         = azurerm_resource_group.nsg.name
  network_security_group_name = azurerm_network_security_group.nsg.name
}

Hi @teamterraform ,Thanks for your rapid reply. But there exist an extra problem, the source_address_prefix and destination_address_prefix is obligatory instead of optional. And user cases failed when:

Error: Invalid value for module argument
TestTerraformNSG 2019-11-14T03:15:46Z command.go:100:
TestTerraformNSG 2019-11-14T03:15:46Z command.go:100:   on ../../modules/HTTP/main.tf line 7, in module "nsg":
TestTerraformNSG 2019-11-14T03:15:46Z command.go:100:    7:   predefined_rules = [
TestTerraformNSG 2019-11-14T03:15:46Z command.go:100:    8:     {
TestTerraformNSG 2019-11-14T03:15:46Z command.go:100:    9:       name = "HTTP"
TestTerraformNSG 2019-11-14T03:15:46Z command.go:100:   10:     },
TestTerraformNSG 2019-11-14T03:15:46Z command.go:100:   11:   ]

My workaround

variable "mat" {
  type = map(any)
  default = {"a":1234}
}

output "aaa" {
  value = lookup(var.mat, "b", null) == null ? "hot dog" : "not a hot dog"
}

I've just found another workaround for this that works well for my case (aws alb listener rule actions) - I switched my var type from list(any) to string and have the consumer pass the value through jsonencode, which I then jsondecode in the module (e.g. for_each = jsondecode(var.any_list)) and use try(setting.value.prop, null).

Was this page helpful?
0 / 5 - 0 ratings