Terraform: Document splat syntax usage in resource block without count

Created on 15 May 2017  ยท  4Comments  ยท  Source: hashicorp/terraform

Hi there,

I stumbled upon the below use case of the splat syntax, and am wondering if this is considered normal behavior we can rely on, or rather a bug that will probably disappear in future versions of terraform.

In the former case, I believe some additional documentation would be great.

Terraform Version

Terraform v0.9.5

Affected Resource(s)

Not resource specific.

Terraform Configuration Files

variable "general" {
  default {
    project = "myproject"
    region  = "europe-west1"
  }
}

resource "google_container_cluster" "gke_cluster0" {
  name               = "${var.general["project"]}-${var.general["region"]}-cluster0"
  zone               = "${var.general["region"]}-${var.gke["main_zone"]}"
  initial_node_count = "${var.gke["initial_node_count"]}"
  network            = "${var.infra["net0_name"]}"
  subnetwork         = "${var.infra["subnet0_name"]}"

  additional_zones = "${formatlist(format("%s-%%s", var.general["region"]), compact(split(",", var.gke["additional_zones"])))}"

  master_auth {
    username = "${var.gke["username"]}"
    password = "${var.gke["password"]}"
  }

  node_config {
    machine_type = "${var.gke["machine_type"]}"
  }
}

resource "google_compute_health_check" "ilb_hc" {
  name = "${var.general["project"]}-${var.general["region"]}-ilb-hc"

  check_interval_sec = 30
  timeout_sec        = 30

  tcp_health_check {
    port = "31181"
  }
}

resource "google_compute_region_backend_service" "ilb_rbs" {
  name = "${var.general["project"]}-${var.general["region"]}-ilb-rbs"

  protocol         = "TCP"
  timeout_sec      = 10
  session_affinity = "NONE"

  backend {
    group = "${element(google_container_cluster.gke_cluster0.instance_group_urls, 0)}"
  }

  backend {
    group = "${element(google_container_cluster.gke_cluster0.instance_group_urls, 1)}"
  }

  backend {
    group = "${element(google_container_cluster.gke_cluster0.instance_group_urls, 2)}"
  }

  health_checks = ["${google_compute_health_check.ilb_hc.self_link}"]
}

Env A:

variable "gke" {
  default {
    username = "" //use terraform.tfvars
    password = "" //use terraform.tfvars

    machine_type       = "n1-standard-2"
    initial_node_count = 3
    main_zone          = "b"
    additional_zones   = ""
  }
}

Env B:

variable "gke" {
  default {
    username = "" //use terraform.tfvars
    password = "" //use terraform.tfvars

    machine_type       = "n1-standard-4"
    initial_node_count = 3
    main_zone          = "b"
    additional_zones   = "c,d"
  }
}

Expected Behavior

Not sure, hence this issue.

Actual Behavior

Terraform dynamically handles backend blocks on the google_compute_region_backend_service resource, as env A has only one instance group backend while env B has 3.

Steps to Reproduce

  1. terraform apply
providegoogle-cloud question

Most helpful comment

Complex types in Terraform are, for lack of a better word, complex. :) I know @apparentlymart has that on his radar and has a lot of thoughts on that, but I don't think we have anything to say about it, officially, just yet.

I don't even know that _every_ block like backend is a Set; I _think_ they could be a list or a map? But I'm not sure. I've mostly seen them as sets.

But yeah, count only works on resources, so it can't be used on fields, which is what blocks are. There was some discussion and talk about how to fix this, but I think it got really thorny really quickly. I think it's still something we want to do, but again, no official promise to do it or any timeline. :)

All 4 comments

Hi @pdecat!

This is an interesting case. Let's walk through what's happening:

  • The backend field of the google_compute_region_backend_service resource is a set. That means each distinct backend will end up in the list no matter how many times it's listed in the resource. Meaning the following has only one backend in the set, no matter how many times it's specified:

    resource "google_compute_region_backend_service" "foo" {
    backend {
      group = "bar"
      description = "baz"
    }
    
    backend {
      group = "bar"
      description = "baz"
    }
    }
    

    Right now, backends are considered distinct only if their description or group is distinct.

  • You're assigning the backend using the element function. As documented, if the element function accesses an index that is past the end of the list, the index will wrap around to the beginning.

The two of these combine to mean that when you have three distinct groups in your list, the set of backends has three distinct items--one per group. However, when you only have one backend, the element function wraps around one the second two backend blocks, meaning the same backend is used in all three blocks. Meaning the set only has one item in it.

So I wouldn't call this a "bug", in that all the pieces are working as intended. I would not, however, call this a "feature", in that overall it's not a designed behaviour. If, in the future, the backend block has a different way of determining if backends are unique, or we switch to using a list instead of a set, this would almost certainly break, as the backends would no longer be deduplicated.

I can't answer whether you should rely on it directly, because only you know the level of risk you're comfortable with. I can tell you that, in my mind, this is working as intended, but I also do not consider it a contract, so it's not being tested for. Hopefully that's enough information to help you make an informed decision. :)

I'm going to close this out, as I think I answered the question. If you want to dig more into this, have a use case you need a more reliable contract for that you're using this as a workaround on right now, or need to talk more about it, feel free to reply below and we'll continue the conversation, or feel free to open a new issue. Thanks!

Hi @paddycarver,

I was not aware of blocks like the backend field of the google_compute_region_backend_service resource being sets, I thought they were lists of maps.

Now it makes perfect sense, thanks for the detailed explanation!

I guess that also kind of explains why we do not have count at the block level.

Complex types in Terraform are, for lack of a better word, complex. :) I know @apparentlymart has that on his radar and has a lot of thoughts on that, but I don't think we have anything to say about it, officially, just yet.

I don't even know that _every_ block like backend is a Set; I _think_ they could be a list or a map? But I'm not sure. I've mostly seen them as sets.

But yeah, count only works on resources, so it can't be used on fields, which is what blocks are. There was some discussion and talk about how to fix this, but I think it got really thorny really quickly. I think it's still something we want to do, but again, no official promise to do it or any timeline. :)

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

oillio picture oillio  ยท  78Comments

shubhambhartiya picture shubhambhartiya  ยท  72Comments

jszwedko picture jszwedko  ยท  77Comments

ncraike picture ncraike  ยท  77Comments

radeksimko picture radeksimko  ยท  80Comments