Terraform templating and loops
Tags: terraform, devops, templating, go
Published: 2019-09-01 23:17:49

---
title: Terraform templating and loops
published: true
tags: terraform,devops,templating,golang
canonical_url: 
---

*UPDATE: as of terraform 0.12, tf now supports a new [template syntax](https://www.terraform.io/docs/configuration/expressions.html#directives)*

You probably already know it’s not easily possible to loop through a variable of type slice/list within templates, in fact, there are no loop constructs within TF templates at all at this point in time.

There is hope though, according to their comment on this GH issue: [https://github.com/hashicorp/terraform/issues/16628](https://github.com/hashicorp/terraform/issues/16628)

So, say we want to generate a JSON file from a slice/array that, once finished, looks something like this :

{% gist https://gist.github.com/alex-leonhardt/852a8c9bf285d1e498f1e8c39fdff67d %}

We cannot simply iterate over an array to change the value of endpointX as we cannot loop through it.

What we’d expect to be able to do, in the template, is something like this

{% gist https://gist.github.com/alex-leonhardt/2a324316fec71da2fa81e3a7fec88813 %}

we’d have to handle that last , but let’s ignore that for now — the idea is simple, iterate over values in the array of map endpoints and use each key and value as variables within that loop to generate the json file. As we already know, this isn’t possible, and we won’t get this anytime soon.

Luckily, necessity is the mother of invention, and I got this to work with the existing loop constructs _outside_ the template; although it’s not as “_clean_” as above, it’s usable and produces the desired result.

#### **_Using TFs buit-in loop construct_**

To make use of TFs built-in loop construct and generate the wanted json, we will need 2 template files, the first will look like this

```
// data.json.tmpl

{
 "name": "${name}",
 "endpoint": "${endpoint}"
}
```

and the second template looks like this

```
// service.json.tmpl

[
 ${value},
 {"links": ${links}}
]
```

Maybe you already know where we’re heading with this, we now render the first template using the built-in loop constructs from TF using the template\_file data resource

```
variable "endpoints" {
_type_ = "list"
_default_ = [
 { endpoint1 = "https://endpoint-1.example.com" },
 { endpoint2 = "https://endpoint-2.example.com" },
 { endpoint3 = "https://endpoint-3.example.com" },
 ]
}

data "template\_file" "data\_json" {
_template_ = "${file("${path.module}/data.json.tmpl")}"
_count_ = "${length(var.endpoints)}"
 vars {
_endpoint_ = "${element(values(var.endpoints[count.index]), 0)}"
_name_ = "${element(keys(var.endpoints[count.index]), 0)}"
 }
}
```

and then re-use the .rendered output as input to the 2nd template, which really just generates the list by pre- and post- fixing the $value variable with [] .

```
variable "links" {
_type_ = "list"
_default_ = [
 "link1",
 "link2",
 "link3",
 ]
}

data "template\_file" "service\_json" {
_template_ = "${file("${path.module}/service.json.tmpl")}"
vars {
_value_ = "${join(",", data.template\_file.data\_json.\*.rendered)}"
_links_ = "${jsonencode(var.links)}"
}}
```

and finally we’re using the output module to show us what we’ve done, this is also how you’d use the result as input to any other TF module that accepts json.

```
output "json" {
_value_ = "${data.template\_file.service\_json.rendered}"
}
```

and the output

```
$ tf apply
data.template\_file.data\_json[2]: Refreshing state...
data.template\_file.data\_json[0]: Refreshing state...
data.template\_file.data\_json[1]: Refreshing state...
data.template\_file.service\_json: Refreshing state...

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

json = [
 {
 "name": "endpoint1",
 "endpoint": "[https://endpoint-1.example.com](https://endpoint-1.example.com)"
},{
 "name": "endpoint2",
 "endpoint": "[https://endpoint-2.example.com](https://endpoint-2.example.com)"
},{
 "name": "endpoint3",
 "endpoint": "[https://endpoint-3.example.com](https://endpoint-3.example.com)"
},
 {"links": ["link1","link2","link3"]}
]
```

#### **_Converting lists/slices to JSON_**

You might have noticed the jsonencode in the last part, this helps to convert any structure to valid json and it becomes valid input to the template as json in essence is just text

```
variable "links" {
_type_ = "list"
_default_ = [
 "link1",
 "link2",
 "link3",
 ]
}

data "template\_file" "service\_json" {
_[...]_
_links_ = "${jsonencode(var.links)}"
}}
```

#### **_References_**

- [TF interpolation syntax](https://www.terraform.io/docs/configuration/interpolation.html)
- [Go Template syntax](https://golang.org/pkg/text/template/#hdr-Variables)
- [GH Issue #16628 — Use Go Template as the template syntax for template\_file data source](https://github.com/hashicorp/terraform/issues/16628)

* * *