Terraform code testing
Terraform code testing is a recurring subject that we keep hearing about. People are wondering a lot about infrastructure code testing, even if we don't see much adoption of testing in practice. First of all, should you test your code, and what are the different strategies to automate tests, and what are the best open source tools to do so?
Terraform code testing is key and yes, you should be testing it. There is a lot of ways you can do it. It is very similar to standard software engineering processes. It comes from the same culture, so there is no surprise here.
Terraform code testing : start by using linters
Linters are tool you can execute directly on your laptop or on your CI/CD system because you want to ensure others respects some coding conventions or standardization.
One linter in Terraform that I really like is named TFLint. It is open source and available on Github. It’s a linter “the infrastructure way”, which means that it lints your code in a way terraform validate wouldn’t. terraform validate is a subcommand in Terraform. Basically, it just checks for the validity of the structure, and not the validity of the contents. It means that if you request a wrong type of AWS machine, terraform validate will not detect it as bogus, while TFLint will.
Here’s how a faulty commit would look like
and here’s how it would be caught and appear in your logs (log view extracted from a CloudSkiff dashboard)
This linter also has a “deep linting” ability that goes beyond simple linting. Imagine that you need to request an existing correct VM, let’s say a
T3 large instance. If your account is not allowed any more capacity (for example you already launched all the VMs you were allowed to request), the linter will send a request to the AWS API for the correctness of what you request plus the ability of your request to be executed within your account limits. So, it obviously does all the things that a proper linter in development will do, like single quotes, quality, tabs, etc… and, it goes beyond the validator and the formatter that you can find directly on Terraform.
One common use case would be that you made all your tests in a staging environment and the AWS limits of this environment are very high because you made dozens of requests over time, each time you needed to test something. So maybe you have hundreds or maybe thousands of allowed instances on each region.
But then you decide to promote a new change in production, and maybe your production environment doesn’t have the same rules, or the same AWS account limits. Then you will promote something that won’t work in your production environment.
The deep linter here will ensure that what you are about to promote or to launch from staging or from development, or any environment to production, really can run as per the rights of your account.
Perform unit testing on your Terraform code with TDD
A proper agile engineering workflow would begin with test-driven development (TDD).
Basically when you start doing some TDD, you write unit tests, like : “well I want to create my VPC and my VPC is supposed to have this address space”. The next step is to write your function (in Terraform it is called a
resource) and then to execute that. As we’ll see later, I would use any
RSpec derivative tools for that: Serverspec, InSpec… Your TDD steps would look something like:
- Start by writing your test : it fails
- Write the resource and it doesn’t fail because it will launch in an isolated environment (especially that VPC).
Through this process you are going to test what you wanted and then destroy it. You can do exactly what you would do in engineering, like in your CI : execute all the tests on pull requests. By the way, choosing how much security you want once you get started on testing is a sensitive subject. I personally execute all the tests prior to merging anything, but I’m much lighter during the pull request phase, because launching all the tests, all the times, at each commit can be very long.
Do integration tests as well
Another thing you can do, and which is quite common in QA processes, is to randomize things, like variables. Terratest is another really cool tool for integration tests. It’s probably the most complete, but also but the most complex. Let’s dig a bit into it :
Terratest is a Golang library. It requires skills to master (well it’s Go…) but allows you to test anything that has an API like AWS, Azure, GCP, kubernetes, Docker images, Packer builds… (Packer is the tool from Hashicorp to build AMIs: virtual machine disk image) and even Helm charts as well…
Basically you just need to create the name of your
resource_test.go, you import your libraries and you just execute this, but it requires a certain level of skills in Golang and a deep understanding of what you want to test.
During this phase of integration, I would randomize items like regions names. You usually always test your code in the same region. By randomizing it, you will have different results, and you will ensure that it works.
Maybe your names are unique, and the names of your resources as well. For example, your
s3 bucket needs to be unique. It is the same process as an engineering project:
- Use the tools
- Lint your code
- Perform unit test on your files
- Randomize that
- Put the results in your CI
- Add that to your pull requests
- Tag your code and your releases, (like
- Use feature branches and then merge to master and then release a code version
Other tools we recommend to test your infrastructure code
I mentioned all the
RSpec derivative tools for that : Serverspec, Inspec… InSpec is made by Chef, while Serverspec is more community based, I think.
Serverspec is made for units testing but it’s not only dedicated to Terraform. It’s more general. You can test anything from infrastructure to Docker images, to processes running on a server or that kind of things, but it’s readable it’s close to English. It’s really generic because it does not test directly the code, or it does not directly match directly the Terraform code in your infrastructure repository, but it does test for the reality of what was launched. So for example, if you add a new feature, maybe a Database on GCP and you want to check that the port “220.127.116.11” is open, then you can just write this test, execute it and then you will be sure until the end of the times that something is answering on this port. Serverspec can help you do this quite quickly and easily.
There is another tool that I really like called GOSS. It is open source and really cool. It is very simple, based on
YAML. It is not meant at all to test Terraform code but rather designed to test results. For example, you created a security group and you want to ensure that
Port 22 for SSH is closed. You can test that, plus processes, packages, DNS resolver, users… or check if your http endpoint on kubernetes is answering, that kind of things…
I think it can even output results of the tests as a Nagios-compatible output so you are even able to launch it as a
/health point in your micro service to ensure that things are running properly. It is really simple and lightweight. I really like this tool. It works very well for Docker files etc …
Wraping things up
So I would say :
- GOSS for very high level, easy stuff with simple YAML, simple binary, very easy, straightforward.
- Serverspec and InSpec for all the unit testing. It’s all open source.
- And Terratest obviously for integration. It is very complete.
Integrate all that in your CI CD system, or at least anywhere before shipping/ releasing it and into the wild or you are going to have issues if you don’t 🙂