Search code examples
listterraformiterationterraform-template-file

Create a single Terraform template_file or local_file by iterating through multiple terraform variable lists


Create a single Terraform template_file or local_file by iterating through multiple terraform variable lists.

I have 3 individual lists of variables. I would like to append a new line to a single "template_file" or "local_file" resource for each iteration through the three lists.

Example:

var.mylist1 = ['name1','name2','name3']
var.mylist2 = ['ip1','ip2','ip3']
var.mylist3 = ['eip1','eip2','eip3']

The output I want:

A single file I can save to a path (./mypath/inventory.ini) that looks like this:

name1 ip-address=ip1 eip-address=eip1
name2 ip-address=ip2 eip-address=eip2
name3 ip-address=ip3 eip-address=eip3

I have attempted to do this with the following, but this creates multiple files, I need this all in ONE SINGLE FILE as the output:

data "template_file" "single_file" {
  count = "${length(var.re-node-public-dns)}"
  #template = "./mypath/inventory.ini"
  template = <<EOL
  $${mylist1} ip-address=$${mylist2} eip-address=$${mylist3}
EOL
  vars = {
    mylist1 = "${var.mylist1[count.index]}"
    mylist2 = "${var.mylist2[count.index]}"
    mylist3 = "${var.mylist3[count.index]}"
  }
}

Solution

  • The template_file data source is deprecated since it was replaced by the built-in template syntax and the templatefile function in Terraform v0.12 and later.

    In this case you are writing an inline template anyway and so you don't need any resource or function and can instead write the template inline as the definition of a local value:

    locals {
      inventory_ini = <<EOL
    %{ for i, name in var.mylist1 ~}
    ${name} ip-address=${var.mylist2[i]} eip-address=${var.mylist3[i]}
    %{ endfor ~}
    EOL
    }
    

    This is an adaptation of the for directive example in Template Directives. Just like with for expressions you can optionally specify two symbols after the for keyword in which case the first one becomes the index of each element.

    As long as all of your lists always have the same number of elements, any index that is valid for the first one will be valid for the other two and so you can use index i to access elements from the other two lists as shown above.


    Although you certainly can pass this information in three separate variables if you wish, note that a more idiomatic way to do this would be a single variable which takes either a map of objects or a list of objects, depending on whether you need to preserve the given order of items.

    Here's an example with a map:

    variable "interfaces" {
      type = map(object({
        ip_address  = string
        eip_address = string
      }))
    }
    
    locals {
      inventory_ini = <<EOL
    %{ for name, interface in var.interfaces ~}
    ${name} ip-address=${interface.ip_address} eip-address=${interface.eip_address}
    %{ endfor ~}
    EOL
    }
    

    The caller would assign the value to this map variable like this:

    interfaces = {
      name1 = {
        ip_address  = "ip1"
        eip_address = "eip1"
      }
      name2 = {
        ip_address  = "ip2"
        eip_address = "eip2"
      }
      name3 = {
        ip_address  = "ip3"
        eip_address = "eip3"
      }
    }
    

    Here's an example with a list:

    variable "interfaces" {
      type = list(object({
        name        = string
        ip_address  = string
        eip_address = string
      }))
    }
    
    locals {
      inventory_ini = <<EOL
    %{ for interface in var.interfaces ~}
    ${interface.name} ip-address=${interface.ip_address} eip-address=${interface.eip_address}
    %{ endfor ~}
    EOL
    }
    

    The caller would assign the value to this list variable like this:

    interfaces = [
      {
        name        = "name1"
        ip_address  = "ip1"
        eip_address = "eip1"
      },
      {
        name        = "name2"
        ip_address  = "ip2"
        eip_address = "eip2"
      },
      {
        name        = "name3"
        ip_address  = "ip3"
        eip_address = "eip3"
      },
    ]
    

    The advantage of using a single collection of objects instead of multiple collections of strings is that the values for a particular interface are inherently grouped together as a single value, so you don't need to do any extra cross-referencing to correlate the values from one list with the values from another.