terraform doesn't allow for executing element(list, index) on empty lists.

Created on 14 Jan 2017  ยท  17Comments  ยท  Source: hashicorp/terraform

I've building a generic module where I _optionally_ allocate the private_ip for the instance. Since terraform doesn't have the ability to optionally set a variable via an if block, I only want private_ip to have a non-null variable in certain cases. In most cases, the ip address is dynamically set by AWS, but on the rare occasion, we want to have it set to an IP (think DB instance), so I used a ternary.

My naive first implementation was as follows (note, we ensure that num_nodes == length(var.private_ips) when the module is being called).

variable "num_nodes" {}
variable "private_ips" {
  default = []
}
...
resource "aws_instance" "coreos" {
  count  = "${var.num_nodes}"
  private_ip = "${length(var.private_ips) > 0 ? element(var.private_ips, count.index) : ""}"
..
}

This caused the following errors to be shown

Errors:

  * element: element() may not be used with an empty list in:

${length(var.private_ip) > 0 ? element(var.private_ip, count.index) : ""}

After much hand-wringing, the following workaround was done, which seems very very hacky.

resource "aws_instance" "coreos" {
  count  = "${var.num_nodes}"
  private_ip = "${length(var.private_ip) > 0 ? element(concat(var.private_ip, list("")), count.index) : ""}"
...
}

Basically, I force element to have a non-empty list by appending an extra blank element to the list (that will never be used) to keep the compiler/parse/runtime interpolator happy.

This works when there are the variable 'private_ips' is empty (the default), but still allows for setting the variable to a non-empty list and assign the ip appropriately, but the implementation feels so very wrong.

Regardless, I'm very happy that setting private_ip to a blank variable 'Does The Right Thing', even if takes some dancing around to make it work. :)

bug config

Most helpful comment

Shout out to Terraform! This tool allow us to programmatically build our infrastructure, which is conceptually amazing. I've learned only today a dozen hacks to circumvent language deficiencies! This is just another one to add to the list. Thanks Terraform!!! :trollface:

All 17 comments

HIL (the interpolation language) doesn't lazy evaluate the branches of the if statement yet. We need to do that, and we have plans too. That is the issue here. Noting that for the future. :)

Similar issue with no if:

 resource "aws_subnet" "foo" {
    vpc_id = ...
    availability_zone = ...
    cidr_block = "${element(var.subnets, count.index)}"
    count = "${length(var.subnets)}"
}

Fails with: * element: element() may not be used with an empty list in: ${element(aws_subnet.foo.*.id, count.index)} under TF 0.8.7.

I believe this is fixed as part of #11704.

Hit that too and ended up with the same workaround

This workaround works with resource variable lists as well:

  ip_configuration {
    ...
    public_ip_address_id = "${length(azurerm_public_ip.virtual_machine_public_ip.*.id) > 0 ? element(concat(azurerm_public_ip.virtual_machine_public_ip.*.id, list("")), count.index) : ""}"
  }

Thanks @pgrunwald. This workaround worked for me as well.

```
public_ip_address_id = "${var.create_public_ip == true ? element(concat(azurerm_public_ip.pi.*.id, list("")), count.index) : ""}"

Hi.
+1
Guys, many thanks for workaround!!!

Shout out to Terraform! This tool allow us to programmatically build our infrastructure, which is conceptually amazing. I've learned only today a dozen hacks to circumvent language deficiencies! This is just another one to add to the list. Thanks Terraform!!! :trollface:

Hi,

I am using element

resource "aws_instance" "instance" {
  count = "${var.count}"

  ami                    = "${var.ami}"
  instance_type          = "${var.instance_type}"
  user_data              = "${var.user_data}"

subnet_id = "${element(var.subnet_id, count.index)}"

I have 3 subnets and it works pretty fine....

Can I randomize the subnet_id line? So that subnet Ids for instances are not chosen serially?

How can this be achieved when the output needs to be a list?

load_balancer_inbound_nat_rules_ids      = [ "${length(var.lb_natrules) > 0 ? element(concat(var.lb_natrules, list("")), count.index) : ""}" ]

In doing the above, I always end up with the following in my plan:

      ip_configuration.0.load_balancer_inbound_nat_rules_ids.#:              "1"
      ip_configuration.0.load_balancer_inbound_nat_rules_ids.0:              ""

...Which fails horribly.

I've tried different permutations, such as using list() instead of surrounding the conditional in square braces, but so far I've not been able to crack it and it's starting to become a huge blocker for one of our modules.

Hello
with a dataSource template_file the work around works as well.

data "template_file" "proxy_userdata" {
  count    = "${var.enable ? length(var.project_env) : 0}"
  template = "${file("${path.module}/user-data.sh.tpl")}"

  vars {
    LDAP_ACTIVATE    = "${var.proxy_activate_ldap}"
    PROXY_ENDPOINT   = "https://${element(concat(var.proxy_endpoint,list("")), count.index)}"
  }
}

Workaround does also the trick also with splat operator

output "read_policy_arn" {
  value = "${element(concat(aws_iam_policy.s3_server_read_policy.*.arn, list("")), 0)}"
}

I found a way to use the Workaround to return an empty list from a condition:

  network_interface_ids = [
    "${var.primaryNetworkInterfaceId[count.index]}",
    "${var.secondaryNetworkInterfaceId[count.index]}",
    "${compact(list(length(var.tertiaryNetworkInterfaceId) > 0 ? element(concat(var.tertiaryNetworkInterfaceId, list("")), count.index) : ""))}",
  ]

Here tertiaryNetworkInterfaceId is a list of network id's that can be empty to not add it as network_interface_id.

This might be handy for @absolutejam

Glad there's a workaround for this interpolation issue, but it really isn't ideal. This workaround for not having lazy evaluation breaks the wrap-around behavior of the element() function in the non-empty case, as there's now an incorrect dummy element at the end. That issue isn't keeping me from using this workaround, just pointing out that the currently accepted solution has limitations.

Any update on where lazy eval for interpolation is on the roadmap?

The conditional operator will only evaluate the selected expression in the next major release of Terraform.

Based on the discussion here it seems like this issue is effectively the same as #15605, in spite of the different proximate cause, so I'm going to close this one just to consolidate discussion over there. Any further updates from the team will be posted on that issue.

Excellent -- thanks for the heads up, @apparentlymart !

I'm going to lock this issue because it has been closed for _30 days_ โณ. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

thebenwaters picture thebenwaters  ยท  3Comments

jrnt30 picture jrnt30  ยท  3Comments

franklinwise picture franklinwise  ยท  3Comments

rjinski picture rjinski  ยท  3Comments

shanmugakarna picture shanmugakarna  ยท  3Comments