Terraform's Data Source Cloudinit
Aug 29, 2017
2 minute read

Overview

Terraform offers a data source for cloudinit which is really useful for bootstrapping EC2 instances with an initial configuration. For me, this is usually a script with enough code to run salt-call. I was having a hard time figuring out exactly how to include files into the data source template_cloudinit_config since my bootstrap script had a dependency on a additional script.

Scenario

In specific provisioning cases I’ve had two scripts that I wanted to include for bootstrapping a Linux EC2 instance via terraform. The problem was that I had one script that referenced the other.

In a simplified example say I have the following shell scripts:

deps.sh:

#!/bin/bash
cat << EOF >> /tmp/output.txt
Hello from dependent script!
EOF

bootstrap.sh:

#!/bin/bash
cat << EOF >> /tmp/output.txt
Hello from bootstrap script!
EOF

/usr/local/bin/deps.sh

How can I ensure that deps.sh exists so that bootstrap.sh correctly calls it via cloudinit? Keeping DRY (Don’t repeat yourself) in mind, there is a way to do this in terraform!

Solution

Assuming there’s a subdirectory of scripts in our terraform directory that contains the above bootstrap.sh and deps.sh scripts, create the following data source inside a new or existing .tf file:

data "template_cloudinit_config" "example" {
  gzip          = true
  base64_encode = true

  part {
    content_type = "text/cloud-config"
    content      = <<EOF
#cloud-config
write_files:
  - content: |
      ${base64encode(file("${path.module}/scripts/deps.sh"))}
    encoding: b64
    owner: root:root
    path: /usr/local/bin/deps.sh
    permissions: '0750'
EOF
  }

  part {
    content_type = "text/x-shellscript"
    content      = "${file("${path.module}/scripts/bootstrap.sh")}"
  }

}

Notice a couple interesting things about the above data source:

  1. The script order matters due to the bootstrap.sh depending on deps.sh
  2. deps.sh is not called directly but instead just put in its expected path with the correct permissions
  3. The content for the deps.sh has to be base64encoded due to the line indents required by the pipe

Then all that’s needed when to referencing user_data in an aws_launch_configuration resource is:

user_data = "${data.template_cloudinit_config.example.rendered}"

Add after a successful bootstrap you should see the following after cat /tmp/output.txt:

Hello from dependent script!
Hello from bootstrap script!

Overall this helped alleviate some of my own needs for custom AMIs and duplicate scripts! I hope this tutorial helps you out as well!