Map variable default value behavior change?

Hi,

Has the behavior for accessing default values in a map variable changed? For example, in our tfvars we might specify:

vm_count = {
  ci = "2"
  hana = "2"
  aas  = "4"
  worker = "0"
}

and the variables.tf contains:

variable "vm_count" {
  type        = "map"

default = {
    dr_ci     = "0"
    dr_aas    = "0"
    dr_hana   = "0"
    dr_worker = "0"
  }
}

Terraform throws an error that it cannot find the default values in the map. It works if we add the dr values to the tfvars, but we set the default so we could avoid needing to do that.

 100:   worker_node_count               = "${var.vm_count["dr_worker"]}"
    |----------------
    | var.vm_count is map of string with 5 elements

The given key does not identify an element in this collection value.

Indeed, map variables in Terraform 0.12 no longer have this special overriding behavior, because it was inconsistent with how maps behave in every other context in Terraform and violated the “explicit is better than implicit” language design principle.

You can read more about this in the upgrade guide section Map variables no longer merge when overridden, which elaborates on the reasoning and also shows how to achieve a similar result more explicitly using the merge function.

@apparentlymart

Why this is not working?

variable "optional_keys" {
  type = object({
    foo = string
    bar = string
    optional = string
  })
}

locals {
  default_values_for_optional_keys = {
    "foo" = "f"
    "bar" = "b"
    "optional" = ""
  }
  merged_map_keys = merge(local.default_values_for_optional_keys, var.optional_keys)
}

output "merged_map_keys" {
  value = local.merged_map_keys
}
Error: Invalid value for input variable

  on terraform.tfvars line 1:
   1: optional_keys = {
   2:   bar = "99"
   4: }

The given value is not valid for variable "optional_keys": attributes "foo"
and "optional" are required.

Should I omit object specification?

variable "optional_keys" {
  type = object({
    foo = string
    bar = string
    optional = string
  })
}

And use it like:

variable "optional_keys" {
  type = map(string)
}

?

I like first specification, because there are explicitly set data types (clearly visible for module input).

Something like this:

variable "optional_keys" {
  type = object({
    foo = string
    bar = string
    optional = string(optional)
  })

  default = {
    optional = "foobar"
  }
}

would be a super usefull for fallbacking to default values…

Object types don’t have a sense of “optional”: they are defined by their set of attributes, so any object that doesn’t have those attributes doesn’t match the type.

For situations where the set of keys is decided by the caller rather than by the module, using map(string) is the best way to model that: Terraform will validate that all of the elements have string values but will not impose constraints on the keys.