Concatenate two lists in Terraform 0.12 – concat()

Terraform 0.12 makes a stronger distinction between list and set values than 0.11 did, and includes some additional checks like this.

In this particular case, concat is failing in this way because concatenation requires all of the elements to have a well-defined order so that the result can also have a well-defined order. Sets are not ordered, so this check is in place to remind you to explicitly select a suitable ordering when converting to list, or to not convert to list at all.

In this particular case it doesn’t seem that the ordering is particularly important, so the lexical ordering implemented by sort could be sufficient:

  subnet_ids = concat(
    sort(data.aws_subnet_ids.private.ids),
    sort(data.aws_subnet_ids.public.ids),
  )

(Because the conversion from list set of string to list of string also imposes lexical ordering, this is functionally equivalent to tolist for string sets. I generally prefer sort here because it’s a clue to a future reader that the result will be in lexical order.)

Another option is to say in the world of sets, and use setunion instead:

  subnet_ids = setunion(
    data.aws_subnet_ids.private.ids,
    data.aws_subnet_ids.public.ids,
  )

Since there should be no duplicates between these two lists it doesn’t really matter which approach you use here, but for completeness I’ll note that in the event that both of these sets contained the same subnet id the setunion operation would dedupe them, because each unique value can only appear zero or one times in a set.


At the time I write this, where count is still the dominant way to create one resource instance per item in a collection, converting to a list eventually is commonly required so that the individual instances can have a required order. Once for_each is implemented, there will be an advantage to using sets rather than lists in situations like this:

resource "aws_instance" "per_subnet_example" {
  # resource-level for_each is not implemented at the time of writing,
  # but planned for a future release.
  for_each = setunion(
    data.aws_subnet_ids.private.ids,
    data.aws_subnet_ids.public.ids,
  )

  # ...
}

When using for_each over a set instead of count, Terraform will identify each instance by the value from the set rather than by consecutive indexes, so one instance from this resource might have the address aws_instance.per_subnet_example["subnet-abc123"], and that means that when elements are added and removed from that set Terraform can just create/destroy the corresponding individual instance rather than potentially recreating everything after the change in the ordered sequence.

Terraform providers are using sets like this in places where it makes sense in order to make this for_each pattern easier to use once it arrives, but unfortunately that means we need to write some extra explicit type conversions in the meantime in order to be explicit that we’re working with these values in an sequence-like way rather than a set-like way.

Leave a Comment