Cannot use for loop for map variable to pass to template


I have the following variable defined:

consul_servers = {
	consul-0 = {
		ip_address = ""

	consul-1 = {
		ip_address = ""

	consul-2 = {
		ip_address = ""

And I’m trying to use a for loop to create a list of the IPs only for the retry_join variable. I’m copy pasting the code partially only:

data "template_cloudinit_config" "vault_server" {
        for_each = var.vault_servers

        part {
                filename = "cloud-init.cfg"
                content_type = "text/cloud-config"
                content = templatefile("${path.module}/files/vault/vault-userdata.tpl", {
                        consul_ca = file("${path.module}/files/ssl-certs-consul/ca/ca.crt")
                        consul_server_crt = file("${path.module}/files/ssl-certs-consul/certs/host.crt")
                        consul_server_key = file("${path.module}/files/ssl-certs-consul/certs/host.key")
                        root_ssh_key = file("${path.module}/files/")
                        provision_node_ssh_key = file("${path.module}/files/")
                        consul_config = templatefile("${path.module}/files/consul/consul-client-config.tftpl", {
                                consul_datacenter = var.consul_datacenter
                                ui_config = "false"
                                server_role = "false"
                                retry_join = [ for ip in var.consul_servers : i["ip_address"] ]
                                consul_domain = var.consul_domain
                                consul_keygen = var.consul_keygen

I don’t understand why this isn’t working in this context. The syntax is right and I’ve tested a similar variable with an output variable without any issues.

It’s just crossed my mind that this might be related to this:

I’ll give it a try and see if it works.

I am obviously blind. I wrote for ip and then i. Funnily enough the last relevant topic I read about this exact same error also involved more or less a typo.

I see that you already solve this but in case it’s useful for future work: Terraform will return a differently-shaped error depending on whether the error is in one of your main configuration files (.tf files) or inside an external template.

If it’s the external template that’s invalid, Terraform will present it as a failure to call the templatefile function, because from the perspective of the language runtime all it knows is that it called a function and the function returned an error. The details of that error will be included in the paragraph at the end of the error message, and should refer to a source location within the template file.

If it’s the templatefile call itself that’s invalid (that is: arguments inside the parentheses after templatefile are not correct) then Terraform typically won’t call the function at all and will instead indicate an error somewhere inside the templatefile expression in the main .tf file.

You didn’t mention what error message you saw in this case, but given the final explanation I assume it was something about a resource reference requiring a resource type and a name, because Terraform would’ve misunderstood your naked i here as a reference to a resource "i" "..." block, which of course doesn’t exist because there isn’t actually any such resource type! This is an unfortunate result of the way Terraform allows directly referring to resources without any special namespace prefix; Terraform must assume that any symbol name that isn’t a builtin or a local value must be intended as a resource reference. :confounded:

Oh, sorry about that, I thought I did paste the error message. Indeed, that’s the error that I got!

After fixing this I did get a different error related to it requiring a string, not the object that I was trying to pass. And yes, the error is very well known on the internet :slight_smile:
I tried using join like that, but then I realised that it wouldn’t add the quotes around the list item:

retry_join = join(",", [for name in var.consul_servers : name["ip_address"]])

Any ideas how I can could pass the list as a literal string in the templatefile with some sort of hack or thereabouts? I haven’t been able to find anything relevant enough yet.

P.S. Indeed, the error referring to the resource was a little bit misleading and it took a while to wrap my head around that! With your explanation it makes sense, though.

It eventually worked like this:

retry_join = join(", ", [for name in var.consul_servers : format("%q", name["ip_address"])])

I had no idea about the %q parameter. I’ve found the answer here: interpolation - How can I convert a list to a string in Terraform? - Stack Overflow

For anyone interested in the documentation for format: