How can I use for_each resultant instances in list

This one has got me scratching my head a bit…

I have the following:

variable "domains" {
  type      = set(string)
  default = [ "domain1", "domain2"]
}

resource "google_compute_managed_ssl_certificate" "certs" {
  for_each = var.domains

  name = "${each.value}"

  managed {
    domains = [ each.value ]
  }

}

resource "google_compute_managed_ssl_certificate" "cert" {
  name = "other-domain"

  managed {
    domains = ["otherdomain"]
  }
 
}

This appears to work perfectly creating resources in GCP as google_compute_managed_ssl_certificate.certs["domain1"] etc

But I’d like to now apply this to part of a load balancer config:

resource "google_compute_target_https_proxy" "https" {
  name          = "https-proxy"
  url_map       = google_compute_url_map.urlmap.self_link

  ssl_certificates =  [ 
    google_compute_managed_ssl_certificate.cert.id,
    google_compute_managed_ssl_certificate.certs[*].id
  ]
}

This will result in an error:

<snip>
google_compute_managed_ssl_certificate.cert.id is "projects/<redacted>/global/sslCertificates/other-domain"
│     │ google_compute_managed_ssl_certificate.certs is object with 2 attributes
│ 
│ Inappropriate value for attribute "ssl_certificates": element 1: string required.

It goes on to tell me that the certs[*].id does not have an attribute named “id”.

I can statically reference one of the domains in the list without issue ( as certs["domain1"].id ) and the attribute “id” is certainly there.

I need to have the one domain “cert” outside of the group “certs” as its also heavily referenced else where.

ssl_certificates seems to be expecting a list of strings but seems to be resolving to objects.

What can I do to get both my single domain cert and list of certs in to the same place for use with the load balancer?

As an aside, unless you’re still using Terraform 0.11 or earlier, just write

  name = each.value

This looks like a job for flatten - Functions - Configuration Language | Terraform by HashiCorp

  ssl_certificates = flatten( 
    google_compute_managed_ssl_certificate.cert.id,
    google_compute_managed_ssl_certificate.certs[*].id
  )

Thanks for you reply @maxb !

The ${} around the each.value in the example is because I removed a key words both before and after the value itself for my example. Someone send it could be sensitive and I didn’t want to argue :slight_smile: ( it was basically cert with a specific identifier on the end :shushing_face: )

Although, I do get your point about the versions - I’m going to try the latest version of TF and see if that makes a difference to this.
At this point in time I’ve had to use 0.15.5 because of some other stuff going on in the same project. Should have made that clear in my initial post. Apologies

I’ve tried the flatten function for that and got the following during the plan stage:

Error: Too many function arguments
│ 
│   on LB.tf line 72, in resource "google_compute_target_https_proxy" "https":
│   70:   ssl_certificates = flatten( 
│   71:     google_compute_managed_ssl_certificate.cert.id,
│   72:     google_compute_managed_ssl_certificate.certs[*].id
│   73:   )
│     ├────────────────
│     │ google_compute_managed_ssl_certificate.cert.id will be known only after apply
│     │ google_compute_managed_ssl_certificate.certs will be known only after apply
│ 
│ Function "flatten" expects only 1 argument(s).
╵

The splat on the certs appears as if its being ignored entirely. :exploding_head:

bah! it still shows the same error with TF 1.1.8 :cry:

This error is saying that flatten expects only a single list to flatten. I think this was just a small mistake on @maxb’s part; they probably meant to pass those two values as elements of a list, like this:

  ssl_certificates = flatten([
    google_compute_managed_ssl_certificate.cert.id,
    google_compute_managed_ssl_certificate.certs[*].id
  ])

Notice the extra [ after flatten( and the extra ] before the closing ).

The flatten function takes a list that might possibly contain other lists and “flattens” those nested lists into their parent, so that the result is guaranteed to be a sequence of non-list values. in this case, since .id is always a string, the result will be a flat list of strings, whereas before (in your original error message) element 1 of the list was itself a list of strings, and thus Terraform reported an error.

I think there is a further problem here that flatten alone won’t solve: google_compute_managed_ssl_certificate.certs is using for_each rather than count and so that expression produces a mapping rather than a sequence (this mapping was the “object with two attributes” that the error message mentioned) and so you’ll need one more step of taking the values from that object in order to make it compatible with [*]:

  ssl_certificates = flatten([
    google_compute_managed_ssl_certificate.cert.id,
    values(google_compute_managed_ssl_certificate.certs)[*].id
  ])

The values function takes a mapping (a map or an object), discards the keys or attribute names and returns just a list of the element/attribute values.

There’s a bunch of stuff going on here so I want to break this down a bit to make sure it’s clear what happened here. In the following I’m going to use simple strings like "a", "b", etc to represent distinct certificate IDs because the exact string values aren’t important here; this is about the shape of the data structure that the strings are within, not the strings themselves.

In your original example, you were setting ssl_certificates something like this:

  ssl_certificates = [
    "a",
    {
      domain1 = "b"
      domain2 = "c"
    }
  ]

Terraform therefore complained that element 1 of that list isn’t a string, and told you it was an object with two attributes. Those two attributes are domain1 and domain2 in the above.

Adding the values function call around google_compute_managed_ssl_certificate.certs turns that object into a list by discarding the attribute names, giving a data structure like this:

  ssl_certificates = [
    "a",
    ["b", "c"]
  ]

This is a bit closer, but still not right: ["b", "c"] is not a string.

So then we get to @maxb’s suggestion of using flatten, which (when combined with values as in my final example above) produces the following final value:

  ssl_certificates = [
    "a",
    "b",
    "c",
  ]

…and this value is now of an acceptable type for the provider’s schema for this ssl_certificates argument, and so it should work.