Video Thumbnail for Lesson
8.2: Testing Terraform Code (Practice)

Testing Terraform Code (Practice)

In this lesson, we'll cover different testing techniques for Terraform code.

We'll walk through static checks, Bash script testing, and Terraform testing using the Terratest package from Gruntwork.

We'll also discuss a powerful test to execute periodic terraform plan commands within your CI/CD system.

Repo Layout

Before we build out any tests, first lets review a reasonable layout for your code repository:

/modules - Define any modules that you are building.
/examples - Working examples of consuming the modules.
/deployed - Different environments such as production and staging.
/test - Contains various tests and checks for your Terraform code.

By structuring things this way, the purpose of each portion of the codebase is clear.

Static Checks

These checks are not tests, but they are useful for analyzing the codebase.

  • terraform fmt: Formats your code according to Terraform's opinionated formatting structure. This can be used in continuous integration to enforce a consistent style guide.
  • terraform validate: Checks if your code is a valid Terraform configuration.
  • Custom validation rules: Use the variable block to enforce certain conditions for variables. For example, enforce a minimum length for a password.
  • Third-party tools: Use third-party tools to scan Terraform configurations for correctness, security, and compliance.

Bash Script

A simple, (yet somewhat rudimentary) approach to testing is to use a Bash script to automate the process of applying, testing, and destroying resources.

The script should:

  1. Navigate to the examples directory.
  2. Run terraform init and terraform apply with the -auto-approve flag.
  3. Wait for the instance to come online (using sleep command).
  4. Issue a curl request to the IP address to check for a successful response.
  5. Destroy resources using terraform destroy when the test completes
#!/bin/bash
set -euo pipefail

# Change directory to example
cd ../../examples/hello-world

# Create the resources
terraform init
terraform apply -auto-approve

# Wait while the instance boots up
# (Could also use a provisioner in the TF config to do this)
sleep 60

# Query the output, extract the IP and make a request
terraform output -json |\
jq -r '.instance_ip_addr.value' |\
xargs -I {} curl http://{}:8080 -m 10

# If request succeeds, destroy the resources
terraform destroy -auto-approve

Terratest

The Terratest package from Gruntwork provides a more robust approach to testing Terraform code using the Go programming language. With Terratest, you can:

  • Specify the number of retries and the time to wait between retries.
  • Use the standard Go testing tooling.
package test

import (
	"crypto/tls"
	"fmt"
	"testing"
	"time"

	"github.com/gruntwork-io/terratest/modules/http-helper"
	"github.com/gruntwork-io/terratest/modules/terraform"
)

func TestTerraformHelloWorldExample(t *testing.T) {
	// retryable errors in terraform testing.
	terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
		TerraformDir: "../../examples/hello-world",
	})

	defer terraform.Destroy(t, terraformOptions)

	terraform.InitAndApply(t, terraformOptions)

	instanceURL := terraform.Output(t, terraformOptions, "url")
	tlsConfig := tls.Config{}
	maxRetries := 30
	timeBetweenRetries := 10 * time.Second

	http_helper.HttpGetWithRetryWithCustomValidation(
		t, instanceURL, &tlsConfig, maxRetries, timeBetweenRetries, validate,
	)

}

func validate(status int, body string) bool {
	fmt.Println(body)
	return status == 200
}

Periodic Terraform Plan Test

One simple test that can be very useful is to periodically execute a terraform plan command within your CI/CD system.

This will notify you of any changes made outside of Terraform, allowing you to investigate and either revert or incorporate the changes into your configuration.