Test Terraform IaC code with Terratest
Terraform is important IaC tool in multi-cloud context. I shared Terraform resources to get started on it. However, for any code, if you cannot test it, it is a big issue in DevOps world. Terratest is a popular tool to test terraform code. It is end to end testing (you cannot unit testing a piece of code or function in terraform code, instead, you provision the infrastructure, then test the end result).
So what can you test? Let’s take look at some samples to find out.
Hello world sample
terraform code
terraform {
# This module is now only being tested with Terraform 0.13.x. However, to make upgrading easier, we are setting
# 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it
# forwards compatible with 0.13.x code.
required_version = ">= 0.12.26"
}# website::tag::1:: The simplest possible Terraform module: it just outputs "Hello, World!"
output "hello_world" {
value = "Hello, World!"
}
The above code does nothing, but return output “Hello, World!”
terraform test
Terratest test code is written in Go, Go test naming convention follows the format of Test + the name of the function under test. Terratest test code usually has 4 parts:
- Terraform options, setting terraform code to test and variables
- Set delayed destroy operation, when function ends, terratest will run the destroy automatically
- Initialize and apply the terraform code to provision infrastructure
- Validate the infrastructure
package testimport (
"testing""github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)func TestTerraformHelloWorldExample(t *testing.T) {
// website::tag::2:: Construct the terraform options with default retryable errors to handle the most common
// retryable errors in terraform testing.
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
// website::tag::1:: Set the path to the Terraform code that will be tested.
TerraformDir: "../examples/terraform-hello-world-example",
})// website::tag::5:: Clean up resources with "terraform destroy" at the end of the test.
defer terraform.Destroy(t, terraformOptions)// website::tag::3:: Run "terraform init" and "terraform apply". Fail the test if there are any errors.
terraform.InitAndApply(t, terraformOptions)// website::tag::4:: Run `terraform output` to get the values of output variables and check they have the expected values.
output := terraform.Output(t, terraformOptions, "hello_world")
assert.Equal(t, "Hello, World!", output)
}
The above test just retrieves output from terraform and use assert.Equal to compare against expected value. The main code is
// package
"github.com/gruntwork-io/terratest/modules/terraform"// retrieve terraform output
output := terraform.Output(t, terraformOptions, "hello_world")
HTTP test
terraform code
The code just provisions EC2 instance serving web contents. So if we can reach the HTTP endpoint, it means the infrastructure is provisioned correctly.
terraform test
The main code is necessary packages, use AWS package to interact with AWS, interact with HTTP endpoint.
// package to interact with AWS
"github.com/gruntwork-io/terratest/modules/aws"# package to interact with HTTP endpoint
http_helper "github.com/gruntwork-io/terratest/modules/http-helper"
AWS code
// Pick a random AWS region to test in. This helps ensure your code works in all regions.
awsRegion := aws.GetRandomStableRegion(t, nil, nil)// Some AWS regions are missing certain instance types, so pick an available type based on the region we picked
instanceType := aws.GetRecommendedInstanceType(t, awsRegion, []string{"t2.micro", "t3.micro"})
Set variables of terraform code
// Construct the terraform options with default retryable errors to handle the most common retryable errors in
// terraform testing.
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
// The path to where our Terraform code is located
TerraformDir: "../examples/terraform-http-example",// Variables to pass to our Terraform code using -var options
Vars: map[string]interface{}{
"aws_region": awsRegion,
"instance_name": instanceName,
"instance_text": instanceText,
"instance_type": instanceType,
},
})
Retrieve terraform output of instance URL
// Run `terraform output` to get the value of an output variable
instanceURL := terraform.Output(t, terraformOptions, "instance_url")
Validate HTTP endpoint (since the code runs immediately after terraform code finishes, but web server takes some time to startup, so you need to set retry to wait web server to be fully up)
// Verify that we get back a 200 OK with the expected instanceText
http_helper.HttpGetWithRetry(t, instanceURL, &tlsConfig, 200, instanceText, maxRetries, timeBetweenRetries)
AWS instance properties
terraform code
It deploys an EC2 Instance and gives that Instance a Name tag.
terraform test
The main code is to use AWS package to retrieve AWS resource properties and validate.
// Run `terraform output` to get the value of an output variable
instanceID := terraform.Output(t, terraformOptions, "instance_id")aws.AddTagsToResource(t, awsRegion, instanceID, map[string]string{"testing": "testing-tag-value"})// Look up the tags for the given Instance ID
instanceTags := aws.GetTagsForEc2Instance(t, awsRegion, instanceID)// website::tag::3::Check if the EC2 instance with a given tag and name is set.
testingTag, containsTestingTag := instanceTags["testing"]
assert.True(t, containsTestingTag)
assert.Equal(t, "testing-tag-value", testingTag)// Verify that our expected name tag is one of the tags
nameTag, containsNameTag := instanceTags["Name"]
assert.True(t, containsNameTag)
assert.Equal(t, expectedName, nameTag)
Summary
On high-level, we can test below
- We can treat the infrastructure as blackbox and interact like end user to validate (there will be complications to use this approach to test scenario like “public cannot access, but can access through a specific VPC/virtual network, since it would be hard to terratest to switch to specific VPC)
- Retrieve output to compare against our expected value
- We can use the cloud provider interface to get the resources and validate properties.
Appendix
Terraform test tools comparison
Other important terraform tools