Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test Terraform files

I'm defining my infrastructure in Terraform files. I like Terraform a lot, but I'm having trouble figuring out how to test. I have awspec, which is really nice and runs RSpec-like tests against the result of your build via the AWS API. But is there a way to do unit tests, like on the results of terraform plan? What kind of workflow are others using with Terraform?

like image 239
David Ham Avatar asked Nov 27 '17 04:11

David Ham


People also ask

How do you test your Terraform files?

Implement integration testingStatic code analysis tools such as lint and format. Run terraform validate to verify the syntax of the configuration file. Run terraform plan to ensure the configuration will work as expected.

How do I test Terraform scripts locally?

To run a test you need to provide a Terraform file and a Go test. Then you simply call go test and Terratest takes care of init ing and apply ing your infrastructure. It performs some tests then destroy s.

How do I validate Terraform files online?

How to run validation. Validation can be run explicitly by using the terraform validate command or implicitly during the terraform plan or terraform apply commands. By default, terraform plan validates the configuration before generating an execution plan.


4 Answers

I'm going to expand on Begin's answer with more information about Kitchen-Terraform.

Kitchen-Terraform is a set of open source plugins that run within Test-Kitchen, these are supposed to go into your Terraform module repository to test that module's functionality before being used in a repository that creates the resources. Please feel free to check the documentation of those two projects for more details, but I will go through my recommendations for integration testing your Terraform code.

Install Ruby, Terraform For this example, the Terraform module repo will be called: my_terraform_module

mkdir -p my_terraform_module
cd my_terraform_module

mkdir -p test/integration/kt_suite/controls \
         test/fixtures/tf_module/

Create a Gemfile:

source "https://rubygems.org/" do
  gem "kitchen-terraform"
end

Install the necessary components (uses the Gemfile for the dependencies of kitchen-terraform)

gem install bundler
bundle install

Create the Test-Kitchen file .kitchen.yml - this brings together the testing frame, Test-Kitchen and Kitchen-Terraform

---
driver:
  name: terraform
  root_module_directory: test/fixtures/tf_module
  parallelism: 4

provisioner:
  name: terraform

transport:
  name: ssh

verifier:
  name: terraform
  groups:
    - name: basic
      controls:
        - file_check
        - state_file

platforms:
  - name: terraform

suites:
  - name: kt_suite

Your Terraform code should be at the root of the Terraform module repository such as:

my_terraform_module/
  |-- main.tf

Example code that can go in main.tf

resource "null_resource" "create_file" {
  provisioner "local-exec" {
    command = "echo 'this is my first test' > foobar"
  }
}

Then we reference the Terraform module just like we would in Terraform live repos - but in a test fixture instead in this file: test/fixtures/tf_module/main.tf

module "kt_test" {
  source = "../../.."
}

Then from there, you can run Terraform apply, but it's done a little differently with Kitchen-Terraform and Test-Kitchen, you run a converge which helps keep track of state and a couple other items.

bundle exec kitchen converge

Now you've seen your Terraform code do an apply, we need to test it. We can test the actual resources that were created, which would be like an integration test, but we can also test the state file, which is a semi unit test, but I am not aware of anything that can currently do unit tests against the HCL code of Terraform.

Create an inspec default profile file: test/integration/kt_suite/inspec.yml

---
name: default

Create an Inspec control for your integration testing: test/integration/kt_suite/controls/basic.rb - I'm using a test for the example Terraform code I used earlier for the main.tf

# frozen_string_literal: true

control "file_check" do
  describe file('.kitchen/kitchen-terraform/kt-suite-terraform/foobar') do
    it { should exist }
  end
end

And this is an example test of pulling information from the state file and testing if something exists in it. This is a basic one, but you can definitely exand on this example.

# frozen_string_literal: true

terraform_state = attribute "terraform_state", {}

control "state_file" do
  describe "the Terraform state file" do
    subject do json(terraform_state).terraform_version end

    it "is accessible" do is_expected.to match /\d+\.\d+\.\d+/ end
  end
end

Then run Inspec controls with Test-Kitchen and Kitchen-Terraform:

bundle exec kitchen verify

I took a lot of this from the getting started guide and some of the tutorials over here: https://newcontext-oss.github.io/kitchen-terraform/getting_started.html

like image 150
nictrix Avatar answered Oct 20 '22 09:10

nictrix


From my research this is a tough issue, since Terraform is not meant to be a full featured programming language and you are declaring what resources you want with Terraform, not how to build them, trying to unit-test doesn't really give you the assurance you are building resources how you'd like without actually running an apply. This makes attempts to unit-test feel more like a linting to me.

However, you could parse your HCL files with something like pyhcl, or parse you're plan files, however from my experience this was a lot of work for little benefit (but I could be missing an easier method!).

Here are some alternatives if you wanted to test the results of your terraform applys:

kitchen-terraform is a tool for writing Test Kitchen specs for your infrastructure.

kitchen-verifier-awspec helps bring together awspec and kitchen-terraform, although I have not used it personally.

If you are using AWS, I have found AWS Config to be able to provide a lot of the same benefits as other infrastructure testing tools, without as much setup/maintenance. Although it is fairly new, and I have not used it extensively.

Also if you are paying for Terraform Premium you get access to Sentinel, which seems to provide a lot of similar benefits to AWS Config, however I have not used it personally.

like image 3
Begin Avatar answered Oct 20 '22 08:10

Begin


We recently open sourced Terratest, our swiss army knife for testing infrastructure code.

Today, you're probably testing all your infrastructure code manually by deploying, validating, and undeploying. Terratest helps you automate this process:

  1. Write tests in Go.
  2. Use helpers in Terratest to execute your real IaC tools (e.g., Terraform, Packer, etc.) to deploy real infrastructure (e.g., servers) in a real environment (e.g., AWS).
  3. Use helpers in Terratest to validate that the infrastructure works correctly in that environment by making HTTP requests, API calls, SSH connections, etc.
  4. Use helpers in Terratest to undeploy everything at the end of the test.

Here's an example test for some Terraform code:

terraformOptions := &terraform.Options {
  // The path to where your Terraform code is located
  TerraformDir: "../examples/terraform-basic-example",
}

// This will run `terraform init` and `terraform apply` and fail the test if there are any errors
terraform.InitAndApply(t, terraformOptions)

// At the end of the test, run `terraform destroy` to clean up any resources that were created
defer terraform.Destroy(t, terraformOptions)

// Run `terraform output` to get the value of an output variable
instanceUrl := terraform.Output(t, terraformOptions, "instance_url")

// Verify that we get back a 200 OK with the expected text
// It can take a minute or so for the Instance to boot up, so retry a few times
expected := "Hello, World"
maxRetries := 15
timeBetweenRetries := 5 * time.Second
http_helper.HttpGetWithRetry(t, instanceUrl, 200, expected, maxRetries, timeBetweenRetries)

These are integration tests, and depending on what you're testing, can take 5 - 50 minutes. It's not fast (though using Docker and test stages, you can speed some things up), and you'll have to work to make the tests reliable, but it is well worth the time.

Check out the Terratest repo for docs and lots of examples of various types of infrastructure code and the corresponding tests for them.

like image 7
Yevgeniy Brikman Avatar answered Oct 20 '22 10:10

Yevgeniy Brikman


In addition to the answers, I will add my two cents. I was not very happy using GO lang with Terratest although it works perfectly well. It is just that GO is not my favorite programming language. I looked for some frameworks in Java and I found terraform-maven. At first glance, I only found examples in Groovy, but since Groovy run on JVM, it is feasible to implement the same examples in Java.

I translated part of the S3PreProvisionSpec.groovy to Java. It is testing this main.tf file.

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class S3PreProvisionTest {

    private final String TF_CHANGE = "change";
    private final String TF_AFTER = "after";
    private final String TF_TAGS = "tags";
    private final Map<String, String> mandatoryTags = Map.of(
        "application_id", "cna",
        "stack_name", "stacked",
        "created_by", "[email protected]"
    );
    private Terraform terraform;
    private TfPlan tfplan;

    @BeforeAll
    void setup() {
        terraform = new Terraform().withRootDir("s3_pre_post_demo")
        // .withProperties(Map.of("noColor", "true"))
        ;
        tfplan = terraform.initAndPlan();
    }

    @AfterAll
    void cleanup() {
        terraform.destroy();
    }

    @Test
    void mandatoryTagsForS3Resources() {
        List<Map> s3Bucket = tfplan.getResourcesByType("aws_s3_bucket");
        System.out.println("=========================");

        s3Bucket.forEach(map -> {
            Map tfChangeMap = (Map) map.get(TF_CHANGE);
            Map tfAfterMap = (Map) tfChangeMap.get(TF_AFTER);
            Map tfTagsMap = (Map) tfAfterMap.get(TF_TAGS);

            assertEquals(3, tfTagsMap.size());
            mandatoryTags.forEach((k, v) -> {
                assertEquals(v, tfTagsMap.get(k));
            });

            try {
                JSONObject jsonObject = new JSONObject(map);
                JSONObject jsonChange = jsonObject.getJSONObject(TF_CHANGE);
                JSONObject jsonAfter = jsonChange.getJSONObject(TF_AFTER);
                JSONObject jsonTags = jsonAfter.getJSONObject(TF_TAGS);
                System.out.println(">>>>>>>>>>>>>>>>>>>> " + jsonTags.toString());
                mandatoryTags.forEach((k, v) -> {
                    try {
                        assertEquals(v, jsonTags.getString(k));
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                });
            } catch (JSONException e) {
                e.printStackTrace();
            }
        });
    }
}
like image 1
Felipe Avatar answered Oct 20 '22 08:10

Felipe