Wednesday, December 26, 2018

Terraform: conditional operator with list values

It's not possible to use list values in a conditional operator. For example, if you try:
resource "aws_elasticsearch_domain" "es_domain" {
  ...

  vpc_options {
    ...
    subnet_ids = [ "${var.es_zone_awareness_enabled ? list("subnet-1", "subnet-2") : list("subnet-1")}" ]
  }

  ...
}
it will fail with "conditional operator cannot be used with list values" message. This is because terraform cannot assert that list's element types are consistent.

The workaround is to join list values into a string to bypass type check and then split string back to list:
resource "aws_elasticsearch_domain" "es_domain" {
  ...

  vpc_options {
    ...
    subnet_ids = [ "${split(",", var.es_zone_awareness_enabled ? join(",", list("subnet-1", "subnet-2")) : join(",", list("subnet-1", "")))}" ]
  }

  ...
}
You might've mentioned that there is an empty string as a second list value in second join function. This is needed to make result string contain a "," character for splitting by final split function.

Of course, it's just a synthetic example with list functions. In real life, those lists come from variables. For instance, I had a map variable which looked like
variable "private_subnet_ids" {
  type = "map"
  default = { "zone-1,zone-2" = "subnet-1,subnet-2" }
}
and I wanted to get a list which contains one or both subnet ids depending on bool variable. Possible steps were:
  1. Turn a map into a list.
  2. If we need both subnets:
    1. Get the first list value (string, "subnet-1,subnet-2").
  3. If we need only one subnet:
    1. Get the first list value (string, "subnet-1,subnet-2").
    2. Split by "," character (list, ["subnet-1", "subnet-2"]).
    3. Get needed list element (string, "subnet-1").
    4. Put it to a list along with an empty string (list, ["subnet-1", ""]).
    5. Join this list into a string by "," character (string, "subnet-1,").
  4. Split by "," character.
Result resource definition looked as:
resource "aws_elasticsearch_domain" "es_domain" {
  ...

  vpc_options {
    ...
    subnet_ids = [ "${split(",", var.es_zone_awareness_enabled ? element(values(var.private_subnet_ids), 0) : join(",", list(element(split(",", element(values(var.private_subnet_ids), 0)), 0), "")))}" ]
  }

  ...
}

2 comments:

  1. This was super helpful, thanks.

    In my use case, the empty string at the end of the ternary condition was causing Terraform to try to add a subnet called "", which it obviously cannot do. So I could apply it as is, then my next `terraform plan` would suggest adding another subnet for "". My plans would always suggest a change, since it was suggesting a change that can never be carried out.

    Wrapping the whole thing in a `compact()` function fixed that though.

    ReplyDelete
    Replies
    1. Thanks for the reply.

      You mean like so `compact(split(",", var.es_zone_awareness_enabled ? element(values(var.private_subnet_ids), 0) : join(",", list(element(split(",", element(values(var.private_subnet_ids), 0)), 0), ""))))`?

      Delete