Terraform: Problems with interpolation if appending to a list

Created on 24 May 2018  ยท  5Comments  ยท  Source: hashicorp/terraform

I have problems with terraform variable interpolation: When we append new elements to a variable (marked as list), the interpolation tells terraform to recreate some existing resources, which didn't change.

Terraform Version

...
$ terraform -version
Terraform v0.11.7
+ provider.null v1.0.0
+ provider.openstack v1.4.0
+ provider.template v1.0.0


Terraform Configuration Files

...
resource "openstack_networking_network_v2" "net" {
  count          = "${length(var.subnet_names)}"
  region         = "${var.region_name}"
  name           = "${var.project_prefix}-${element(var.subnet_names,count.index)}-net"
  admin_state_up = "true"
}

resource "openstack_networking_subnet_v2" "subnet" {
  depends_on = ["openstack_networking_network_v2.net"]
  region     = "${var.region_name}"
  network_id = "${element(openstack_networking_network_v2.net.*.id,count.index)}"
  count      = "${length(var.subnet_names)}"
  cidr       = "${lookup(var.subnet_cidrs,element(var.subnet_names,count.index))}"
  ip_version = 4
  name       = "${var.project_prefix}-${element(var.subnet_names,count.index)}-sub-net"

  allocation_pools = {
    start = "${lookup(var.subnet_reserved_starts,element(var.subnet_names,count.index))}"
    end   = "${lookup(var.subnet_reserved_ends,element(var.subnet_names,count.index))}"
  }

  gateway_ip      = "${lookup(var.subnet_gateway_ips,element(var.subnet_names,count.index))}"
  enable_dhcp     = "true"
  dns_nameservers = ["${var.dns_list}"]
}

resource "openstack_networking_router_interface_v2" "dmz_port" {
  depends_on = ["openstack_networking_subnet_v2.subnet"]
  region    = "${var.region_name}"
  count     = "${length(var.subnet_names)}"
  router_id = "${var.router_id}"
  subnet_id = "${element(openstack_networking_subnet_v2.subnet.*.id,count.index)}"
}


Expected Behavior

If I add a new element to the list "var.subnet_names", I should see one newly created net, subnet and dmz_port.

Actual Behavior

I see the newly created resources. However, it also tries to modify all existing subnets and dmz_ports (but not the existing net). Tthe order of the elements in the list didn't change, also indicated by the fact that the configuration of the subnet didn't change. If I run terraform plan, I see the output similar to this:

...
-/+ module.landscape.openstack_networking_router_interface_v2.dmz_port[0] (new resource required)
      id:                        "266d1f61-02ab-4472-9658-ff6ab9743387" => <computed> (forces new resource)
      port_id:                   "266d1f61-02ab-4472-9658-ff6ab9743387" => <computed>
      region:                    "eu-1" => "eu-1"
      router_id:                 "cb7bcead-7630-4acb-9aef-36d2fd9dbb77" => "cb7bcead-7630-4acb-9aef-36d2fd9dbb77"
      subnet_id:                 "5c953e1d-29c2-41a9-abb7-87060a02aafb" => "${element(openstack_networking_subnet_v2.subnet.*.id,count.index)}" (forces new resource)


-/+ module.landscape.openstack_networking_subnet_v2.subnet[0] (new resource required)
      id:                        "5c953e1d-29c2-41a9-abb7-87060a02aafb" => <computed> (forces new resource)
      allocation_pools.#:        "1" => "1"
      allocation_pools.0.end:    "192.168.101.9" => "192.168.101.9"
      allocation_pools.0.start:  "192.168.101.2" => "192.168.101.2"
      cidr:                      "192.168.101.0/24" => "192.168.101.0/24"
      dns_nameservers.#:         "2" => "2"
      dns_nameservers.0:         "172.18.4.23" => "172.18.4.23"
      dns_nameservers.1:         "172.18.4.24" => "172.18.4.24"
      enable_dhcp:               "true" => "true"
      gateway_ip:                "192.168.101.1" => "192.168.101.1"
      ip_version:                "4" => "4"
      ipv6_address_mode:         "" => <computed>
      ipv6_ra_mode:              "" => <computed>
      name:                      "admin-sub-net" => "admin-sub-net"
      network_id:                "7aba8a42-344c-4d27-b381-fc0b4da7a4f5" => "${element(openstack_networking_network_v2.net.*.id,count.index)}" (forces new resource)
      no_gateway:                "false" => "false"
      region:                    "eu-1" => "eu-1"
      tenant_id:                 "0013aa96217b4d6ebc71f0fecb854489" => <computed>

config question

Most helpful comment

Hi @thoHeinze! Sorry for the delayed response.

In the current version of Terraform, there is a restriction where passing a list that contains any values that are <computed> to a function will force the function result to always be <computed> itself.

In your configuration, that applies to expressions like element(openstack_networking_subnet_v2.subnet.*.id,count.index). Since element is a function, _all_ of the values in openstack_networking_subnet_v2.subnet.*.id must be known in order for it to return a known result.

To avoid this, use the native indexing syntax rather than the element function:

  subnet_id = "${openstack_networking_subnet_v2.subnet.*.id[count.index]}"

In general, the element function should be used only if you need its special "wrap around" behavior when the given index is greater than the length of the list. In the configuration you shared here, that doesn't seem to be the case.

In the next major version of Terraform (currently under development) this rule will be loosened so that the element function is able to treat unknown elements the same way as the index syntax does, but we will continue to recommend to use the native syntax unless the wrap-around behavior is needed.

All 5 comments

Can somebody help here (e.g. @apparentlymart) ? I just like to understand if something is wrong with my terraform code or if this is a known issue.

Hi @thoHeinze! Sorry for the delayed response.

In the current version of Terraform, there is a restriction where passing a list that contains any values that are <computed> to a function will force the function result to always be <computed> itself.

In your configuration, that applies to expressions like element(openstack_networking_subnet_v2.subnet.*.id,count.index). Since element is a function, _all_ of the values in openstack_networking_subnet_v2.subnet.*.id must be known in order for it to return a known result.

To avoid this, use the native indexing syntax rather than the element function:

  subnet_id = "${openstack_networking_subnet_v2.subnet.*.id[count.index]}"

In general, the element function should be used only if you need its special "wrap around" behavior when the given index is greater than the length of the list. In the configuration you shared here, that doesn't seem to be the case.

In the next major version of Terraform (currently under development) this rule will be loosened so that the element function is able to treat unknown elements the same way as the index syntax does, but we will continue to recommend to use the native syntax unless the wrap-around behavior is needed.

I have same problem and was trying to fix it replacing element with native index syntax.
Unfortunately I ran into problem described at https://github.com/hashicorp/terraform/issues/18051
Looks like the problem is with specific dependency graph:
resourceA(list) -> datasource(list) -> resourceB(list)
If one resource is missing in A, native index doesn't work in datasource (index 19 out of range for list) even if count is 20.
If I use element in datasource, it causes all resources in B to recreate.

@apparentlymart I tried your suggestion and it works for me. I will close this issue.

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