Terraform templatefile with nested loop

Hi

Is it possible to use a nested loop in templating like the one below? I currently have the first loop working correctly, but I’m encountering a problem with the nested one, it inserts 2 rows, when I’m expecting only one

locals {
  prom_jobs  = ["job1", "job2"]
  sd_configs = ["config_1.json", "config_2.json"]
}
%{ if try(prom_jobs, []) != [] ~}
%{for prom_job in prom_jobs ~}
- job_name: ${prom_job)
%{for config_file in sd_configs ~}
  file_sd_condifgs:
  - ${config_file)
%{ endfor ~}
  basic_auth:
    username: 'foo'
    password: 'bar'
%{ endfor ~}
%{ endif ~}

Here is Templated output. I’m expecting to have one config file per job, instead, it gives me 2

- job_name: job1
  file_sd_configs:
  - config_1.json
  - config_2.json <-- shouldn't be here
  basic_auth:
    username: foo
    password: bar
- job_name: job2
  file_sd_configs:
  - config_1.json <-- shouldn't be here
  - config_2.json 
  basic_auth:
    username: foo
    password: bar

Hi @menvol3,

If the job names and the “sd configs” are related to each other then the best way to describe that is as a single collection of objects so that it’s clear how the values are correlated. Since your jobs have unique keys a map is probably the most ergonomic data type:

locals {
  prom_jobs = tomap({
    job1 = {
      sd_configs = tolist([
        "config_1.json",
      ])
    }
    job2 = {
      sd_configs = tolist([
        "config_2.json",
      ])
    }
  })
}

This expression produces a value of type map(object({ sd_configs = list(string) })). You can then pass this whole map as one variable prom_jobs to the template.

Since you appear to be generating YAML, you should follow the advice in Generating JSON or YAML from a template and use the yamlencode function instead of trying to build YAML using string concatenation:

${yamlencode([
  for name, job in prom_jobs : {
    job_name        = name
    file_sd_configs = job.sd_configs
    basic_auth = {
      username = "foo"
      password = "bar"
    }
  }
])}

Using yamlencode will make it Terraform’s responsibility to produce valid YAML syntax, and so you won’t need to worry about producing correct indentation, quoting, and escaping.


If you need additional per-job settings in future, you can add additional attributes to all of the job objects and then describe in the template how to use them. For example, if you wanted to make the basic_auth setting vary by job, you could extend the local value like this:

locals {
  prom_jobs = tomap({
    job1 = {
      sd_configs = tolist([
        "config_1.json",
      ])
      username = "foo"
      password = "bar"
    }
    job2 = {
      sd_configs = tolist([
        "config_2.json",
      ])
      username = "foo"
      password = "bar"
    }
  })
}

…and then access those new attributes in the template:

${yamlencode([
  for name, job in prom_jobs : {
    job_name        = name
    file_sd_configs = job.sd_configs
    basic_auth = {
      username = job.username
      password = job.password
    }
  }
])}

That is exactly what I was searching for

Thank You @apparentlymart :handshake:

This topic was automatically closed 62 days after the last reply. New replies are no longer allowed.