Terraform Provisioners

Svg Vector Icons : http://www.onlinewebfonts.com/icon

Create a Free Account

Signup today to secure your 10 free lab credits. Limited time.

As you already know, Terraform is an infrastructure as code tool that allows us to programmatically create resources for running software. Apart from creating, modifying, and destroying these infrastructures, Terraform does not, traditionally, interfere in what goes on in the infrastructure.

\ Oftentimes, after creating an infrastructure, you will want to run a script or an automation tool (such as Ansible) on the newly created infrastructure. To further extend its features and allow you to easily perform such actions after creating or deleting a resource, Terraform provides a block called provisioner.

\

What are Terraform Provisioners?

Provisioners are a Terraform feature that allows you to run scripts, commands or upload files after creating or destroying an infrastructure. When used in your configuration code, provisioners help to automatically invoke specific actions, commands, or scripts that you, otherwise, may have to run manually. You can use Terraform provisioners to install packages, pass information, or install configuration management tools on a resource.

Terraform offers three main types of provisioners: file, local-exec, and remote-exec.  More on that later in this lesson.

\ Before using Terraform provisioners, you must note the following rules:

  • A provisioner will only run once.
  • You can run a provisioner when applying a configuration or when destroying it.
  • You can run multiple provisioners in the same configuration file.
  • The provisioner block must be nested inside a resource block.
  • If an infrastructure has been created before you add the provisioner block, the provisioner will not execute. This is because provisioners only execute immediately after the creation or destruction of a resource.

\ Below is an example of the usage of a provisioner block:

\ json provisioner "file" { source      = "conf/myapp.conf" destination = "/etc/myapp.conf" }

\

Provisioner Arguments

Let’s learn about some of the Terraform blocks and arguments you will use when working with provisioners.

\ * connection (required): a connection block is used to define the connection credentials for the provisoner to use to connect to the resource. The connection block supports both ssh and winrm connections. The following is an example of a provisioner block:

json connection { type     = "ssh" user     = "root" password = var.root_password host     = self.public_ip }

\ If you're running multiple provisioners in your Terraform configuration code, you can nest the connection block inside the provisioner block:

\ ```json provisioner "remote-exec" { connection {
type = "ssh"
user = "ubuntu"
host = self.public_ip
private_key = file("")
}

...other provisioner code

} ```

\ If there is no connection block nested inside a provisioner block, the provisioner will default to use a connection block declared outside it.

\ * source: the source argument is used in the file and remote-exec provisioner types to specify the source of a file you want to copy to the newly created infrastructure. You can either use source or content argument for file provisioners.

```json

file source

source = "conf/myapp.conf"

script  source

source = "script.sh" ```

\ * content: this is used in the file provisioner type. Instead of specifying a file source, you can also write content directly to the destination of the provisioner.

json content = "hello world!"

\ * destination (required): specifies the destination where you want to save a source file or content on the newly created resource. It is used in both the file and remote-exec provisioner types.

json destination = "/etc/myapp.conf"

\ * command (required): is used to specify a command Terraform should run after creating or destroying a resource. It is used in local-exec provisioners.

json command = "echo ${self.private_ip} >> private_ips.txt"

\ * inline: this argument accepts an array of command strings for a remote-exec provisioner to run. It is used in remote-exec provisioners.

json inline = [ "chmod +x /tmp/script.sh", "/tmp/script.sh args", ]

\ * when: by default, Terraform executes providers after you run terraform apply. You can tell Terraform to run the provisioner after a terraform destroy by setting the value of when argument to destroy.

json when = destroy

\ * working_dir: it is used in local-exec provisioners to specify the directory where a command should be executed. The directory path can be written as a relative path to the current directory or an absolute path. The default is your current working directory.

\ * interpreter: it is used in local-exec and remote-exec provisioners to specify which interpreter should be used to run the command. It is an array that accepts the actual interpreter as the first item, and a path as the second item.

json interpreter = ["PowerShell", "-Command"]

\ * on_failure: if a provisioner is not executed successfully when creating a resource, Terraform will mark it as a tainted. That is, when next you run terraform apply, Terraform will destroy the resource and recreate it. You can alter this behavior by setting the on_failure argument to continue. on_failure=”fail” is the default.

\

Types of Terraform Provisioners

Let's see some examples of each of the three common provisioner types.

\

File Provisioner

The file provisioner allows you to copy files or file directories from the machine you're running Terraform to a newly created resource. A typical file provisioner looks like the example below:

\ ```json

...resource declaration code

provisioner "file" { source      = "conf/myapp.conf" destination = "/etc/myapp.conf" } ```

\ This code passes the myapp.conf file from the local path to the /etc/myapp.conf destination path in the newly created infrastructure.

\

Local Provisioner

local-exec provisioner is used to invoke an action, command, or script in the local machine that is running Terraform. Once used as the type of a provisioner block, it executes the command that is defined in the command argument.

\ ```json

...resource declaration code

provisioner "local-exec" { command = "echo ${self.private_ip} >> private_ips.txt" interpreter = ["PowerShell", "-Command"] } ```

\ The code above creates a file named private_ips.txt file, then saves the private IP of the resource inside.

\

Remote Provisioner

Similar to local-exec, remote-exec executes commands or scripts. However, this type of provisioner runs the command or script on the newly created resource.

```json

...resource declaration code

provisioner "remote-exec" { inline = [ "puppet apply", "consul join ${aws_instance.web.private_ip}", ] } ```

\

Lab Time!

In this lab session, you will create file, local-exec, and remote-exec provisioners to invoke specific actions after creating an EC2 instance.

\

Start the Lab

If you are using the Skillmix Lab engine, you can start the lab with these instructions. Otherwise, you can continue to the next step with your own account.

\ * Click on the “Lab is offline” button on the top right of the page. * Click on the Start Lab button when the sidebar opens. * Wait for all the credentials to appear.

\

Configure the AWS CLI Profile

Once the lab credentials are prepared, make sure AWS CLI is installed before configuring the AWS CLI profile.

The following is how you can configure the AWS CLI profile on various operating systems:

\ Linux/Mac: open this file with a text editor ~/.aws/credentials

Windows: open this file with a text editor %USERPROFILE%.aws credentials.

Alternatively: Manually locate the .aws folder on your computer, then navigate to the "credentials" file and open it in a text editor.

\ ```json

...other profiles

[skillmix-lab] aws_access_key_id= aws_secret_access_key= ```

\ Remember to replace <lab access key> and <lab secret key> with the access key and secret key you get from the Lab Environment you started above.

\

Create an EC2 Key pair

  • Navigate to the EC2 dashboard from the AWS console.
  • In the navigation pane on left, under Network & Security, choose Key Pairs

\ \

\ \ * Choose Create Key pair on top right corner

\ \

\ \ * Fill in key information

Name: skillmix-instance-key

Key pair type : RSA

Private key file format: .pem.

\ Then, click on create key pair.

\ \

\ \ AWS will automatically download your private key pair once the creation is successful. You will use this key pair later in the lab.

\

Create Project Directory

Open your terminal/command line and, run the following command to create our project directory:

```bash

create the project directory

$ mkdir terraform-provisioners ```

\

Create Workspaces

Since we intend to create three different provisioners independently, this is a good avenue to use Terraform workspaces. If you haven’t learned about workspaces before, that’s ok. Just follow the steps for now.

\ We will create 3 workspaces, one for each type of provisioner.

\ Run the following commands, one after the other to create the workspaces:

```bash

create local-exec provisioner workspace

$ terraform workspace new local-provisioner

create file provisioner workspace

$ terraform workspace new file-provsioner

create remote-exec provisoner workspace

$ terraform workspace new remote-provisioner ```

\ Now, we will create a configuration file and populate it.

\ Firstly, run terraform workspace select local-provisoner to switch into the local-provisioner workspace.

\ Create a main.tf file inside the workspace:

```bash

switch to the workspace

$ terraform workspace select local-provisoner

create the main.tf

$ touch main.tf ```

\ Open the main.tf file; input, and save the following code:

```bash terraform { required_providers { aws = { source  = "hashicorp/aws" version = "~> 4.0" } } } provider "aws" { profile = "skillmix-lab" region  = "us-west-2" } resource "aws_instance" "web_server" { ami           = "ami-08d4ac5b634553e16" instance_type = "t2.micro" tags = { Name = "skillmix-lab-instance" }

provisioner "local-exec" { command = "echo ${self.private_ip} >> my_private_ip.txt" } } ```

\ Code Review

  • The self keyword in the code allows us to tap into the newly created EC2 instance.
  • The command argument saves the private IP of the EC2 instance inside a my_private_ip.txt file in our current directory.

\ Create the EC2 Instance Resource

Now we will run the terraform workflow commands to see the local-exec provisioner in action

\ ```bash

initialize terraform

$ terraform init

apply the configuration

$ terraform apply -auto-approve ```

\ Once the EC2 instance has been successfully created, you can inspect your current working directory, and you will see the newly created my_private_ip.txt file

\ You can open the file and will find the private IP address in it.

\ \

\ \ After seeing the local-exec provisioner in action, run terraform destroy to destroy the EC2 instance we created.

\

Create File Provisioner

Next we will switch to the file-provisioner workspace and write a new configuration.

\ Run terraform workspace select file-provisoner to switch into our file-provisioner workspace.

\ ```javascript

switch to the right workspace

$ terraform workspace select file-provisoner ```

\ Open the main.tf file; update it with following code:

```json terraform { required_providers { aws = { source  = "hashicorp/aws" version = "~> 4.0" } } } provider "aws" { profile = "skillmix-lab" region  = "us-west-2" }

resource "aws_instance" "web_server" { ami           = "ami-08d4ac5b634553e16" instance_type = "t2.micro" tags = { Name = "skillmix-lab-instance" }

#connection block connection { type     = "ssh" user     = "ubuntu" host     = self.public_ip private_key = file("") }

#file provisioner block provisioner "file" { content = "My Skillmix EC2 Instance" destination = "/home/ubuntu/skillmix-file.txt" } }

output the public ip

output "ip" { value = aws_instance.web_server.public_ip } ```

\ Code Review

  1. In the connection block:

  2. we're connecting to the instance through SSH.

  3. Since we're using an Ubuntu AMI to create our instance, the default user value is "ubuntu"
  4. the host argument taps into the new instance we create to extract and use the public IP as its value.
  5. the private_key argument specifies the path to your private key. Replace <path-to-private-key> with the path to the key pair you downloaded earlier.

  6. The provisioner block will create a skillmix.txt file with the defined content in the /home/ubuntu/skillmix-file.txt path of the EC2 instance to be created.

  7. In the output block, we are printing the public ip of the created instance to the terminal so that we can use it to SSH into the instance.

\ Create the EC2 Instance Resource

Now we will run the terraform commands to see the file provisioner in action.

\ Run following commands:

```bash

initialize terraform

$ terraform init

apply the configuration

$ terraform apply -auto-approve ```

\ Take note of the IP address that Terraform outputs on the CLI.

\ After applying the configuration and Terraform successfully creates our EC2 instance, we will SSH into the instance to be sure that the provisioner block worked.

\ Run the following commands:

```bash

SSH into the destination folder using the outputted ip address

$ ssh -i "/home/ubuntu/skillmix-file.tx" ubuntu@

view the content of the file

$ cat skillmix-file ```

\ This will display our content value from the provisioner block on the command line.

\ Run terraform destroy to destroy the EC2 instance resource.

\

Create Remote Provisioner

Lastly, we will work with the remote provisioner. To start, switch to the right workspace:

\ ```javascript

switch workspaces

$ terraform workspace select remote-provisoner ```

\ Open the main.tf file and update it with the following code:

\ ```bash terraform { required_providers { aws = { source  = "hashicorp/aws" version = "~> 4.0" } } } provider "aws" { profile = "skillmix-lab" region  = "us-west-2" }

resource "aws_instance" "web_server" { ami           = "ami-08d4ac5b634553e16" instance_type = "t2.micro" tags = { Name = "skillmix-lab-instance" }

connection block

connection { type     = "ssh" user     = "ubuntu" host     = self.public_ip private_key = file("") }

remote provisioner

provisioner "remote-exec" { inline = [ "touch /home/ubuntu/skillmix.txt" ] } }

output the public ip

output "ip" { value = aws_instance.web_server.public_ip } ```

\ Code Review

  • The connection and output blocks are the same as in the file provisioner code we used earlier.
  • The provisioner block will create a skillmix.txt file with the defined content in the /home/ubuntu/skillmix-file.txt path of our EC2 instance.

\ Create the EC2 Instance Resource

Now, we will run the terraform workflow commands to see the remote-exec provisioner in action.

Run the following commands:

\ ```bash

initialize terraform

$ terraform init

apply the configuration

$ terraform apply -auto-approve ```

\ Take note of the IP address that is printed as an output on the CLI.

\ After applying the configuration and Terraform successfully creates our EC2 instance, we will SSH into the instance to be sure that the provisioner block worked.

\ Run the commands below to SSH into the instance and view the files in the directory:

```bash

SSH into the destination folder using the outputted ip address

$ ssh -i "/home/ubuntu/" ubuntu@

list the content of the directory

$ ls ```

\ This will list the folders and files in that directory. You should see the skillmix.txt file we created by running a touch command via the remote provisioner as one of the listed files.

\

Delete resources

Once you complete this lab, run terraform destroy to remove all the resources you created.


Svg Vector Icons : http://www.onlinewebfonts.com/icon

Create a Free Account

Signup today to secure your 10 free lab credits. Limited time.