Testing Terraform with Terratest
2020-03-06
This should be another short one. One of the advantages of IaC (infrastructure as code) is the fact you can test the things you write. There are multiple tools to test your Terraform configs with, but Gruntwork has created an awesome Go package to test your Terraform configs called Terratest. The advantage to Terratest is you just write Go like you would with anything else and it just works.
Here’s an example module to create an ec2 instance:
main.tf
provider "aws" { region = "us-east-1"}resource "aws_instance" "web" { ami = "ami-0f544554edba5e8ed" instance_type = "t2.micro" tags = { Name = var.tag_name }}
provider "aws" { region = "us-east-1"}resource "aws_instance" "web" { ami = "ami-0f544554edba5e8ed" instance_type = "t2.micro" tags = { Name = var.tag_name }}
vars.tf
variable "tag_name" { description = "EC2 tag name"}
variable "tag_name" { description = "EC2 tag name"}
outputs.tf
output "instance_id" { value = aws_instance.web.id}
output "instance_id" { value = aws_instance.web.id}
This is obviously a very simple module. It creates an ec2 instance and takes a single variable for the tag called NameName. It then has an output variable called instance_idinstance_id. To test this we can write a small Go test to check that the tag exists and is correct.
package testimport ( "testing" "github.com/gruntwork-io/terratest/modules/aws" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert")func TestTerraform(t *testing.T) { tagName := "testing_tag" region := "us-east-1" terraformOptions := &terraform.Options{ TerraformDir: "../", Vars: map[string]interface{}{ "tag_name": tagName, }, } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) instanceID := terraform.Output(t, terraformOptions, "instance_id") instanceTags := aws.GetTagsForEc2Instance(t, region, instanceID) testTag, containsTag := instanceTags["Name"] assert.True(t, containsTag) assert.Equal(t, tagName, testTag)}
package testimport ( "testing" "github.com/gruntwork-io/terratest/modules/aws" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert")func TestTerraform(t *testing.T) { tagName := "testing_tag" region := "us-east-1" terraformOptions := &terraform.Options{ TerraformDir: "../", Vars: map[string]interface{}{ "tag_name": tagName, }, } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) instanceID := terraform.Output(t, terraformOptions, "instance_id") instanceTags := aws.GetTagsForEc2Instance(t, region, instanceID) testTag, containsTag := instanceTags["Name"] assert.True(t, containsTag) assert.Equal(t, tagName, testTag)}
I have been putting my tests in a teststests directory with my module. Then I can run go test -v ./tests/...go test -v ./tests/... to test the module.
The function TestTerraformTestTerraform passes the value testing_tagtesting_tag to the module’s tag_nametag_name variable. It then does a terraform initterraform init and a terraform applyterraform apply. We defer the terraform destroyterraform destroy until the end of the tests.
Then we get the instance id from the output variable and pass that to the GetTagsForEc2InstanceGetTagsForEc2Instance function. It grabs the tags for us and then we use the assert package to ensure the tag exists and is the correct one.
This is obviously a very simple test, but you can easily build on these concepts and have them run directly in your CI pipeline when you are working on your modules.