Terraform – Provisioners

Introduction

Terraform Provisioners allow you to execute custom scripts/code on the remote machine which is being provisioned or something locally on which the terraform code is being executed. There are various provisioners which are available in terraform. In this blog, we will look at the generic type of provisioners – file, local and remote provisioners. The blog builds from this previous blog and is divided into the following sections.

All the code for these sections are available on the github as well. The link is provided at the end of each section.

File Provisioner

File provisioner allows you to transfer files locally on which terraform code is being executed to the resource which is being provisioned. For example, you want to copy a configuration file or directory from the local to your EC2 instance. This can be easily accomplished by File provisioner.

Here is the example of the instance.tf which would allow us to copying a file.

resource "aws_instance" "blog6-ec2" {
  ami = lookup(var.cw_ami, "eu-west-2") # Variable added.
  instance_type = var.cw_instance_type # Variable added.
  key_name = aws_key_pair.my_blog_key.key_name
  security_groups = ["cw-blog-6-sg"]

  provisioner "file" {
    source = "conf/my-conf.ini"
    destination = "/home/ec2-user/my-conf.ini"
  }

  connection {
    host = self.public_ip
    type = "ssh"
    user = "ec2-user"
    private_key = file("ssh-keys/myblog-key")
  }
}

output "myIp" {
  value = aws_instance.blog6-ec2.public_ip
}

Let’s analyse the code for instance.tf

  • Line 2 – Uses a standard lookup function to get the image Id.
  • Line 3 – Uses the value in the variable to find the instance type.
  • Line 4 – Gets the Keys provisioned.
  • Line 5 – Gets the security group provisioned.
  • Line 7 – Declares the file provisioner.
  • Line 8 – Provides source file name to copy to the provisioned ec2 instance.
  • Line 9 – Provides destination file name for the file to be copied to the provisioned ec2 instance.

Terraform requires that connection details be provided in case of a file provisioner. The reason for that is that terraform does not know how to connect to the EC2 instance which is being provisioned.

  • Line 12- Provides a configuration for creating a connection to the provisioned ec2 instance.
  • Line 13 – Connect to an IP address. Which is the public IP address of the provisioned ec2 instance.
  • Line 14 – Provides the connection type. In this case, it is ssh.
  • Line 15 – Provides the Linux username
  • Line 16 – Provides the private key to use.

Notice that there is a section which talks about the connection details.

All the remaining code remains unchanged and is available on this link

Let’s see the file provisioner in action. You can execute the code using the following commands

terraform init
terraform apply

While provisioning in the terminal screen you should see that it is executing the file provisioner.

File provisioner in action

You can also login to the AWS EC2 instance using SSH. See our configuration file present in /home/ec2-user

EC2 SSH terminal session

Couple of things to keep in mind before we move on.

  • There can be multiple file provisioners.
  • File provisioner can also copy directories.

Below is the instance.tf which has multiple provisioners configured and copying files as well. All the remaining code remains unchanged and is available on this link

resource "aws_instance" "blog6-ec2" {
  ami = lookup(var.cw_ami, "eu-west-2") # Variable added.
  instance_type = var.cw_instance_type # Variable added.
  key_name = aws_key_pair.my_blog_key.key_name
  security_groups = ["cw-blog-6-sg"]

  # Copy a file
  provisioner "file" {
    source = "conf/my-conf.ini"
    destination = "/home/ec2-user/my-conf.ini"
  }

  # Copy a directory
  provisioner "file" {
    source = "conf"
    destination = "/home/ec2-user"
  }

  connection {
    host = self.public_ip
    type = "ssh"
    user = "ec2-user"
    private_key = file("ssh-keys/myblog-key")
  }
}

output "myIp" {
  value = aws_instance.blog6-ec2.public_ip
}

Let’s see the file provisioner in action. You can execute the code using the following commands. Assuming you have already initialised.

terraform apply

While provisioning in the terminal screen you should see that it is executing the file provisioner.

File provisioner in action

You can also login to the AWS EC2 instance using SSH. See our directory and configuration file present in /home/ec2-user

EC2 SSH terminal session

As you can see a directory has been copied and a file has also been transferred as had been configured in instance.tf above.

Let’s move on to the next provisioner.

Local Provisioner

A local provisioner allows you to execute some code on the local machine on which your terraform code is being executed. The custom code/script is executed only after the resource has been provisioned. For the purposes of this blog, let’s assume, you want to execute a shell script on the local machine once the resource has been provisioned. Our instance.tf will look like below.

The main thing to focus in this piece of code is the local-exec section of the code which describes what script to execute locally. Also observer there are no remote connection details required.

resource "aws_instance" "blog6-ec2" {
  ami = lookup(var.cw_ami, "eu-west-2") # Variable added.
  instance_type = var.cw_instance_type # Variable added.
  key_name = aws_key_pair.my_blog_key.key_name
  security_groups = ["cw-blog-6-sg"]

  provisioner "local-exec" {
    command = "./scripts/my-local-script.sh ${aws_instance.blog6-ec2.public_ip}"
  }

output "myIp" {
  value = aws_instance.blog6-ec2.public_ip
}

Here is a simple script(my-local-script.sh) which I want to fire once the provisioning is complete. BTW – make sure that your script is executable.

echo "Hello from ****Local Provisioner*******"
echo "Here is the public IP passed to this script $1"

However, a more pertinent example would be local provisioner executing a script which creates an ansible dynamic inventory. This inventory would then be used by an ansible playbook to provision software on the newly provisioned resources. A much cleaner way of separating cloud provisioning and software provisioning – ❤Ansible. It may be time for cloudwalker to expand 🙂 – interested anyone!

Back to the task at hand – All of the above code(and related) is available on GitHub on this link. Below is the output of the execution. It shows the output generated via the shell script as well.

Local provisioner in action

Remote Provisioner

Finally, coming to the remote provisioner, you may have guessed it by now. It allows you to execute commands on the newly provisioned resource. The mechanics are pretty much similar to the above two provisioners. We have to provide two things

  • Connection details for the provisioned resource
  • Remote Command to execute

So here goes our trusty instance.tf

resource "aws_instance" "blog6-ec2" {
  ami = lookup(var.cw_ami, "eu-west-2") # Variable added.
  instance_type = var.cw_instance_type # Variable added.
  key_name = aws_key_pair.my_blog_key.key_name
  security_groups = ["cw-blog-6-sg"]

  provisioner "remote-exec" {
    script = "./scripts/my-remote-script.sh"
  }

  connection {
    host = self.public_ip
    type = "ssh"
    user = "ec2-user"
    private_key = file("ssh-keys/myblog-key")
  }
}

output "myIp" {
  value = aws_instance.blog6-ec2.public_ip
}

The above code does the following

  • Copying a script from the local machine which is executing our terraform code to the remote EC2 instance.
  • Executing the script on the remote EC2 instance.

There are a couple of ways of executing remote commands/scripts with remote provisioner. The above describes how a script which is stored locally is copied to remote resource and executed. You can also execute individual commands via inline argument

The script executed is given below. All the github code is available on this link

echo "Hello from ****Remote Provisioner*******"
echo "****We are going to run a yum update****"
sudo yum -y update

The logs for the execution are shown below.

Remote provisioner in action

This brings us to the end of this long post on Terraform Provisioners. Hope you find this post helpful and useful in your work. If you like it please do share it. Will be back with the new blog post!

Leave a Comment