The feature image in the article shows a vacuum cleaner in reference to how to keep your Terraform code clean
Photo by No Revisions on Unsplash

How To Keep Your Terraform Code Clean, The Sustainable Way

Where do I start to create good Terraform code for the long term? Discover the best practices and tools to get you off to the right start

Guillaume Vincent
Guillaume Vincent

Table of Contents

Working in a DevOps team implies contributing to many Terraform repositories. Sometimes we don't have all the same way of writing Terraform code. Code reviews becomes also time-consuming and maintaining code is tough.

As you go along, you'll own more and more Terraform code and one day will always be 24 hours. How to keep a good quality level with the smallest of effort? This time is precious, you must not waste it.

Here, automation is your salvation to keep your Terraform code clean. You'll be able to handle more code with less effort. Then, invest the freed-up time on more impactful tasks.

This article shows you the first steps to start this continuous improvement. This concerns best practices and tools. Terraform has built-in tools, others are to add to your config.


Keep The Same Format Everywhere

Terraform fmt

When you read a non-standard code, your eye has to adapt all the time. This requires more focus to keep up. You must standardize the code format to ensure good readability.

Terraform provides a built-in subcommand named fmt. It rewrites your code to follow subsets of the Terraform language style conventions.

$ terraform fmt

πŸ’‘ Formatting rules may change between Terraform versions. Don't forget to run terraform fmt command after a version upgrade.

Naming convention

One of the main difficulties in computer science is to name things well. The same applies to Terraform for variables, outputs, resources, and data sources. And this is where a good naming convention comes into play.

This will allow you to have a clear and unambiguous code. It will read itself as English.

In this article, I won't go into the whole naming convention. However, I invite you to consult this link which offers an inspiring one:

Naming conventions - Terraform Best Practices


Track Typos and Syntax Errors

Terraform validate

Previously, you have seen the fmt command of Terraform. Terraform validate is another built-in subcommand. It will allow you to check and track syntax errors and typos.

Let's take the following example:

provider "aws" {
  region = "us-east-1"
}

resource "aws_instanc" "foo" {
  ami           = "ami-0ff8a91507f77f867"
  instance_type = "t2.small"
}

The snippet contains an error at the resource name. Running validate subcommand on it highlights it:

$ terraform validate
β•·
β”‚ Error: Invalid resource type
β”‚
β”‚   on main.tf line 5, in resource "aws_instanc" "foo":
β”‚    5: resource "aws_instanc" "foo" {
β”‚
β”‚ The provider hashicorp/aws does not support resource type  
| "aws_instanc". Did you mean "aws_instance"?
β•΅

Terraform code might look valid from the Terraform eyes but in reality may not work. In this example, the code declares an inexisting AWS provider region and a wrong instance type:

provider "aws" {
  region = "missing"
}

resource "aws_instance" "foo" {
  ami           = "ami-0ff8a91507f77f867"
  instance_type = "wrong" # invalid type!
}

Terraform indicates all is correct when running validate:

$ terraform validate
Success! The configuration is valid.

This is because Terraform verifies only the code syntax and structure. It does not check the values used in the AWS providers and resources. This is outside the Terraform scope and related to the provider one.

πŸ’‘ Consider Terraform validate as the first step for quick verification. It does not detect all the errors before terraform apply.

TFLint

TFLint is a linter checking potential Terraform errors and enforcing best practices. You can add plugins compatible with major cloud providers – AWS, Azure, GCP – for advanced error detection. Best practices are configurable through a rule system.

GitHub - terraform-linters/tflint: A Pluggable Terraform Linter
A Pluggable Terraform Linter. Contribute to terraform-linters/tflint development by creating an account on GitHub.

TFLint looks always for additional .tflint.hcl file in the current directory where it runs. In this configuration file, you add specific rules and plugins.

Here is an example of TFLint configuration:

config {
  force               = false
  disabled_by_default = false
}

plugin "aws" {
  enabled = true
  version = "0.12.0"
  source  = "github.com/terraform-linters/tflint-ruleset-aws"
}

# Enabled by default. Check more AWS rules: https://github.com/terraform-linters/tflint-ruleset-aws/blob/master/docs/rules/README.md
rule "aws_instance_invalid_type" {
  enabled = true
}
  1. The plugin AWS is added to detect specific errors.
  2. A rule to detect invalid AWS instance types is enforced.

Now, let's take the previous Terraform AWS code snippet:

provider "aws" {
  region = "us-east-1"
}

resource "aws_instance" "foo" {
  ami           = "ami-0ff8a91507f77f867"
  instance_type = "wrong" # invalid type!
}

You have to initialize TFLint in your terraform directory:

$ tflint --init

Next, run the TFLint with the directory location:

$ tflint .
1 issue(s) found:

Error: "wrong" is an invalid value as instance_type (aws_instance_invalid_type)

  on main.tf line 7:
   7:   instance_type = "wrong"

πŸ’‘ Consider Terraform validate as the first step for quick verification. It does not detect all the errors before the terraform apply.


Security and Compliance

Terraform is nothing more or less than a tool for managing infrastructure as code. It is up to you to ensure a good security design. This is important to check for misconfiguration that may lead to a vulnerability.

What is Checkov?

Checkov is a static code analysis tool for scanning infrastructure as code files. It supports Terraform and includes more than 750 predefined policies. You can also contribute and create your own custom policies.

Install Checkov

$ brew install checkov

Evaluate the Terraform plan

Create a JSON file from the Terraform plan:

$ terraform init
$ terraform plan --out tfplan.binary
$ terraform show -json tfplan.binary | jq '.' > tfplan.json

Checkov read this file and returns outputs with recommendations:

The Checkov output of the Terraform plan
The Checkov output of the Terraform plan 

Pre-Commit: One Tool To Master Them All

What is pre-commit?

Pre-commit is a framework to manage git hook scripts. They are useful for identifying simple issues before submission to code review. Before you even type a commit message, pre-commit hooks are run.

pre-commit

Pre-commit replaces many commands you type to check your code.

Install pre-commit

$ brew install pre-commit

It is also possible to install it with pip:

$ pip install pre-commit

How to use pre-commit?

The Terraform code needs to be in an initialized git repository. Pre-commit expects a configuration named .pre-commit-config.yaml. In this file, you define the pre-commit hooks for Terraform.

Initialize the git repository:

$ git init

Here is the pre-commit configuration:

repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.62.3
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_docs
        args:
          - '--args=--lockfile=false'
      - id: terraform_tflint
        args:
          - '--args=--only=terraform_deprecated_interpolation'
          - '--args=--only=terraform_deprecated_index'
          - '--args=--only=terraform_unused_declarations'
          - '--args=--only=terraform_comment_syntax'
          - '--args=--only=terraform_documented_outputs'
          - '--args=--only=terraform_documented_variables'
          - '--args=--only=terraform_typed_variables'
          - '--args=--only=terraform_module_pinned_source'
          - '--args=--only=terraform_naming_convention'
          - '--args=--only=terraform_required_version'
          - '--args=--only=terraform_required_providers'
          - '--args=--only=terraform_standard_module_structure'
          - '--args=--only=terraform_workspace_remote'
      - id: checkov
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.1.0
    hooks:
      - id: check-merge-conflict
      - id: end-of-file-fixer
      
.pre-commit-config.yaml

The pre-commit config uses hooks from 2 different repositories. The first is Terraform-specific, and the second one is more generic. It runs terraform fmt, terraform validate, tflint you have seen before. The order of the hook definition is the execution order.

Once the config is ready, you need to add the Terraform files in git:

$ git add main.tf

You can run pre-commit manually with this command:

$ pre-commit run -a

Pre-commit has executed all the hook scripts and updated the Terraform file. It needs to re-add the file in git to take into account the update. Pre-commit is automatically run when committing the code.


Conclusion

Through this guide, you have seen the main thing to get started and keep your Terraform code clean. This relies on the implementation of good practices and the use of tools like linters and scanners.

With pre-commit, you can automate the checking of potential errors locally in a single command. The checks are done automatically before committing the code to the remote git repository. This will make your code reviews more efficient.

Pre-commit will allow you to streamline your CI/CD pipelines. It has many hook scripts to drive many Terraform linters and scanners.


Thanks and I hope this article helped you with Terraform! And you, do you have a different way of doing things or other tools? Share your tips in the comments!

Infrastructure as Code

Guillaume Vincent Twitter

DevOps Engineer & AWS Certified Solution Architect. Cloud enthusiast and automation addict