DEV Community

Joseph D. Marhee
Joseph D. Marhee

Posted on

Up and Running with templates in Terraform

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, 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:


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

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

function install_kube_tools() {
 apt-get update && apt-get install -y apt-transport-https
 curl -s | apt-key add -
 echo "deb 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 manifest, we have our compute resource defined:

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:

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.

Top comments (0)