Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
nateww opened this issue Jan 14, 2017 · 17 comments
Closed

Comments

@nateww
Copy link

nateww commented Jan 14, 2017

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. :)

@mitchellh
Copy link
Contributor

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. :)

@pikeas
Copy link
Contributor

pikeas commented Feb 28, 2017

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.

@apparentlymart
Copy link
Contributor

I believe this is fixed as part of #11704.

@kaii-zen
Copy link

Hit that too and ended up with the same workaround

@percygrunwald
Copy link

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) : ""}"
  }

@codyja
Copy link

codyja commented Jul 11, 2017

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) : ""}"

discordianfish added a commit to discordianfish/tectonic-installer that referenced this issue Jul 12, 2017
@andrey-iliyov
Copy link

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

@guilhermeblanco
Copy link

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:

@ranvijayj
Copy link

ranvijayj commented Feb 2, 2018

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?

@absolutejam
Copy link

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.

@dthauvin
Copy link

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)}"
  }
}

@Ketouem
Copy link

Ketouem commented Apr 30, 2018

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)}"
}

@jpotma
Copy link

jpotma commented Jul 13, 2018

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

@john-osullivan
Copy link

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?

@apparentlymart
Copy link
Contributor

apparentlymart commented Jul 17, 2018

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.

@john-osullivan
Copy link

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

@ghost
Copy link

ghost commented Nov 14, 2019

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.

@ghost ghost locked and limited conversation to collaborators Nov 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests