nginxsplitterraformelement

Using Element and Split Gets First Item Rather than Last Item in Terraform


We're trying to apply a dynamic name to a firewall rule for opening 8089 and 8843 in GCP using terraform based on the list of instance group urls. Instead of taking that result and giving us the last item in the url, it gives us https:

tf:

#This is to resolve an error when deploying to nginx
  resource "google_compute_firewall" "ingress" {
  for_each      = toset(google_container_cluster.standard-cluster.instance_group_urls)
  description   = "Allow traffic on ports 8843, 8089 for  nginx ingress"
  direction     = "INGRESS"
  name          = element(split("/", each.key), length(each.key))
  network       = "https://www.googleapis.com/compute/v1/projects/${local.ws_vars["project-id"]}/global/networks/${local.ws_vars["environment"]}"
  priority      = 1000
  source_ranges = google_container_cluster.standard-cluster.private_cluster_config.*.master_ipv4_cidr_block
  target_tags = [
    element(split("/", each.key), length(each.key))
  ]

  allow {
    ports = [
      "8089",
    ]
    protocol = "tcp"
  }
  allow {
    ports = [
      "8443",
    ]
    protocol = "tcp"
  }
}

Result:

    Error: "name" ("https:") doesn't match regexp "^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$"

  on main.tf line 133, in resource "google_compute_firewall" "ingress":
 133:   name          = element(split("/", each.key), length(each.key))

What is the solution here? Why is it not giving the last item in the array? Is there a better way?


Solution

  • Like with many languages, Terraform/HCL uses zero based indexing so if you want the last element in an array you need to subtract one from the length like this:

    locals {
      list = ["foo", "bar", "baz"]
    }
    
    output "last_element" {
      value = element(local.list, length(local.list) - 1)
    }
    

    The element function is causing this confusion because instead of getting an out of bounds/range error when you attempt to access beyond the length of the list it wraps around and so you are getting the first element:

    The index is zero-based. This function produces an error if used with an empty list. The index must be a non-negative integer.

    Use the built-in index syntax list[index] in most cases. Use this function only for the special additional "wrap-around" behavior described below.

    To get the last element from the list use length to find the size of the list (minus 1 as the list is zero-based) and then pick the last element:

    > element(["a", "b", "c"], length(["a", "b", "c"])-1)
    c
    

    Unfortunately, at the time of writing, Terraform doesn't currently support negative indexes in the built-in index syntax:

    locals {
      list = ["foo", "bar", "baz"]
    }
    
    output "last_element" {
      value = local.list[-1]
    }
    

    throws the following error:

    Error: Invalid index
    
      on main.tf line 6, in output "last_element":
       6:   value = local.list[-1]
        |----------------
        | local.list is tuple with 3 elements
    
    The given key does not identify an element in this collection value.
    

    As suggested in the comments, a better approach here would be to first reverse the list and then take the first element from the reversed list using the reverse function:

    output "last_element" {
      value = reverse(local.list)[0]
    }