Posted on

Gruntworks open-sourced their terraform, docker, packer etc. testing framework and I simply find it amazing. To my knowledge, there is nothing comparable out there.

Let’s take a look at a simple example. You can find the code at https://github.com/datacisions/terraform-examples. What we will do:

  1. Write a terratest to check whether a particularly labeled EC 2 instance is there;
  2.  Run it an see it fail;
  3. Write the terraform code necessary to make it pass, to deploy the instance and label it.
  4. Run the test again to see it pass.
  5. Write a second, slightly more robust test for the same “component” (the EC 2 instance).
  6. Fix this bug in the terraform code
  7. See the second test pass.

To setup terraform see https://www.terraform.io/intro/getting-started/install.html. Make sure to configure your go paths correctly, see https://github.com/golang/go/wiki/SettingGOPATH. Put this project in your go-workspace. You can check your setup with

$ terraform - -version
>…
$ echo $GOPATH
> ...
Now, without any further ado, let us create a blank project in our go-workspace:
— src
—— main.tf               contains the AWS components
—— outputs.tf            defines what outputs we want from terraform (like some id)
—— variables.tf          contains variables we want to be able to set (e.g. instance size)
— test
— — firstExample_test.go     contains our “test”.
Keep the files blank for now.

1. Write a terratest

Let’s say you want to deploy an ec2 instance, then test that it’s there. Let’s think for a second about a test for this. What I usually do is to launch my instance with some test-label, then head over to the AWS console and check for that label. Then I might grab the IP and try to connect to it.

So that sounds like a plan, let’s write a test that at least checks whether an instance with a predefined label is there:

This code points the engine to the terraform code to come with terraformDir : “../“, then it sets a bunch of options including a “test_label” and the expectedName which has a random number to not collide with any other instances. The command terraform.InitAndApply(t, terraformOptions) then launches terraform.
Then we use the function aws.GetEc2InstanceIdsByTag to do exactly what we would do by hand, that is to find the instance by it’s tag. Finally assert.Equal compares the output of the terraform run with what we get from AWS.

2. Run the test and see it fail

Put the code above in the test folder and call it *_test.go. Then type:
$ go test -v
> --- FAIL: TestEc2InstanceProvisioning (109.93s)
…
FAIL
exit status 1
FAILterraform-examples/test109.950s
Great! So now we know we still have to do some work, but we don’t have to: Deploy the script, log into the AWS console, navigate to the right place,  and check whether your instance is there, then terminate everything. In 109s you got your result.

3. Let’s fix this

To make things simple, we use a fixed ami id for now. The newest ami id in us-west-1 is
ami-b70554c8. Let’s fix our variable first, then our outputs and then the main terraform code:
# ---------------------------------------------------------------------------------------------------------------------
# OPTIONAL PARAMETERS
# These parameters have reasonable defaults.
# ---------------------------------------------------------------------------------------------------------------------
variable "region" {
  description = "AWS region to deploy the instances"
  default = "us-east-1"
}

variable "instance_name" {
  description = "The name tag to set for the EC2 Instance."
  default     = "testing-example-instance"
}

variable "test_label" {
  description = "Tag to identify the instance as test instance."
  default = "yes"
}

variable "ami_id" {
  description = "Id of the ami to deploy, depends on region."
  default = "ami-b70554c8" // for region us-east-1
}
This defines four simple variables, the region, an instance name for our name tag, the ami id we just mentioned and a label to identify the instance as a test instance. Save this as variables.tf. The outputs.tf is:
output "instance_id" {
  value = "${aws_instance.example.id}"
}

output "instance_private_ip" {
  value = "${aws_instance.example.private_ip}"
}
Technically we would just need the instance_id, but the IP is always nice to have. Finally the actual main.tf:
# ---------------------------------------------------------------------------------------------------------------------
# DEPLOY AN EC2 INSTANCE RUNNING UBUNTU
provider "aws" {
  region = "${var.region}"
  profile= "admin"

}

resource "aws_instance" "example" {
  ami           = "${var.ami_id}"
  instance_type = "t2.micro"

  tags {
    Name = "${var.instance_name}"
    Test = "${var.test_label}"
  }
}
This HCL script chooses the provider “aws” and provisions one t2.micro instance and the predefined tags.

4. Run the test again, to see it pass

go test -v
should now show you a passing test.

5. Making things more interesting

We fixed an ami-id right now. For multiple reasons, it’s a good idea to test your infrastructure deployment in more than one region. And luckily terratest provides some very simple ways of making that work. So let’s try it with the following code:
The code above first specifies a list of possible regions to select from. Then it finds the newest image in that region as AWS ami-ids are only available in a specified region. It then passes that ami-id to the terraform script as a variable and does the same things as before.

6. Run terratest again

Now if you call go test -v you should see two passing tests:
TestEc2InstanceProvisioningWithRandomRegion 2018-07-14T21:42:16+02:00 command.go:96: Destroy complete! Resources: 1 destroyed.

...

--- PASS: TestEc2InstanceProvisioningWithRandomRegion (106.61s)

....
--- PASS: TestEc2InstanceProvisioning (127.41s)
PASS

ok  terraform-examples/test 127.432s
And that’s it! You’ve just written your first tests for infrastructure as code.

Leave a Reply