Up and Running with templates in Terraform
Tags: terraform, configmanagement, systemadministration
Published: 2018-12-16 23:11:21

---
title: Up and Running with templates in Terraform
published: true
description: A quick how-to for using templates with Terraform
tags: terraform,config-management,system-administration
---

Terraform has a lot of great formatting and templating tools, but one in particular I make heavy use of is the `template_file` [data source](https://www.terraform.io/docs/providers/template/d/file.html), which in my case, makes it super simple to pass in variable data from my cloud provider resources (and other resources generated locally when I run Terraform, and then managed in my remote state store) to these templates and use them, in my example, with `cloud-init` when asking my provider to create a compute resource.

So, let's start with a simple use case where this is useful. My use cases is in provisioning a Kubernetes cluster, so for my cluster nodes, the cloud-init script (in my example, just a bash script, but this can be used for any text resource) needs the cluster controller IP address that API server is running on, the Kubernetes version being used, and the cluster token.

My script is pretty simple in this regard, and the template is saved as `node.tpl`:

```bash
#!/bin/bash

function install_docker() {
 apt-get update; \
 apt-get install -y docker.io

 cat << EOF > /etc/docker/daemon.json
 {
   "exec-opts": ["native.cgroupdriver=cgroupfs"]
 }
EOF
}

function install_kube_tools() {
 apt-get update && apt-get install -y apt-transport-https
 curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
 echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list
 apt-get update
 apt-get install -y kubelet=${kube_version} kubeadm=${kube_version} kubectl=${kube_version}
}

function join_cluster() {
    kubeadm join "${primary_node_ip}:6443" --token "${kube_token}" --discovery-token-unsafe-skip-ca-verification
}
```

and you'll see I call variables like `kube_version`, `kube_token`, and `primary_node_ip` to pass in variable values from the Terraform manifest, which we'll cover in a moment.

In the `node.tf` manifest, we have our compute resource defined:

```hcl
resource "digitalocean_droplet" "k8s_node" {
  name      = "${format("${var.cluster_name}-node-%02d", count.index)}"
  image     = "ubuntu-16-04-x64"
  count     = "${var.count}"
  size      = "${var.primary_size}"
  region    = "${var.region}"
  private_networking = "true"
  ssh_keys  = "${var.ssh_key_fingerprints}"
  user_data = "${data.template_file.node.rendered}"
}
```

and you'll see for `user_data` (the DigitalOcean provider idiom for cloud-init), we're referencing `data.template_file.node.rendered`, which will contain the rendered version of our template above, but in order to render this, we need to, first, create the resource, and then populate those values, local to that resource:

```hcl
data "template_file" "node" {
  template = "${file("${path.module}/node.tpl")}"

  vars {
    kube_token      = "${random_string.kube_init_token_a.result}.${random_string.kube_init_token_b.result}"
    primary_node_ip = "${digitalocean_droplet.k8s_primary.ipv4_address}"
    kube_version    = "${var.kubernetes_version}"
  }
}
```

So, you'll see I set my `vars` to a variety of different sources, some are referencing attributes from another resource (my `k8s_primary` node's `primary_ipv4_address`, for example, and a user variable `kubernetes_version`, and then a random string generated elsewhere in my project).

Then, when you proceed to `apply` Terraform, the `user_data` field will be populated by the rendered template, with the data you provided substituted in for the relevant variables.