Seamless Code Control: Embrace Pre-Commit For Effortless Quality

Santiago Miguez Deagustini
Globant
Published in
6 min readNov 8, 2023
Picture created with DALL-E 3

As a Cloud and DevOps Engineer, one of my responsibilities is to build new infrastructure from scratch. Thanks to many geniuses out there who developed tools like Terraform, CloudFormation, or Azure Resource Manager, just to mention some examples, we can perform this task using code; better said, reusing code! But with great power comes great responsibility, so we need to perform some additional tasks into our code to ensure that it is, for example, well-formatted, easy to understand, and in compliance with security standards and naming conventions.

To make all these refinements and checks possible, we can use specialized tools to help us, and it is common to find them implemented during the execution of the Continuous Integration pipelines. But we can go one step further and avoid waiting for its execution and fix our code before committing it to the repository. This is where the Pre-Commit tool, a framework for managing and maintaining multi-language pre-commit hooks, comes in handy. This article will be focused on the implementation of Pre-Commit as a commit hook for our Infrastructure as Code (IaC) code.

Prerequisites

Before starting working with Pre-Commit, we need to install it and some necessary tools that will be needed as well. As mentioned earlier, we will focus on IaC code using Terraform and Terragrunt, so we are going to use them alongside tfsec, Checkov, TFLint, and terraform-docs.

Important: As a macOS M1 user, all steps will be for this platform, but I will include links to install them on other platforms supported by these tools. Also, I assume a working Python3 environment and Homebrew are installed.

From a terminal, run the following commands to install:

  • The Pre-Commit framework software.
$ pip3 install pre-commit

Or you can use Homebrew.

$ brew install pre-commit
  • The Terraform and Terragrunt tools.
#Terraform
$ brew tap hashicorp/tap
$ brew install hashicorp/tap/terraform
#Terragrunt
$ brew install terragrunt
  • The specialized check tools.
$ brew install tfsec
$ brew install checkov
$ brew install tflint
$ brew install terraform-docs

IaC Base Code

We need some code to see how Pre-Commit works. I created some Terraform and Terragrunt code to deploy a Resource Group, a Storage Account, and a Virtual Network into Microsoft Azure. You can review the code in this repository. The structure of the code is the following:

Directories and files in the repository

Pay extra attention to the lack of README.md files in the modules' directories. One of our goals is to create this kind of document automatically.

Pre-Commit In Action

Now that everything is set up, we need to create a .pre-commit-config.yaml file in the repository's root. This file contains every step that we are going to execute, and it looks like this:

repos:
- repo: https://github.com/terraform-docs/terraform-docs
rev: v0.16.0
hooks:
- id: terraform-docs-go
args: ["-c", ".terraform-docs.yaml", "./modules/resource_groups"]
- repo: https://github.com/terraform-docs/terraform-docs
rev: v0.16.0
hooks:
- id: terraform-docs-go
args: ["-c", ".terraform-docs.yaml", "./modules/storage_account"]
- repo: https://github.com/terraform-docs/terraform-docs
rev: v0.16.0
hooks:
- id: terraform-docs-go
args: ["-c", ".terraform-docs.yaml", "./modules/virtual_networks"]
- repo: https://github.com/gruntwork-io/pre-commit
rev: v0.1.22
hooks:
- id: terraform-validate
- id: tflint
- id: terragrunt-hclfmt
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.83.4
hooks:
- id: terraform_fmt
args:
- --args=-write=true
- --args=-recursive
- id: terraform_checkov
- id: terraform_tfsec
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-merge-conflict
- id: check-yaml
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace

Let us take a look at the content of this file:

  • The Terraform-docs step will create a README.md file for every Terraform module we pass as a variable. The file .terraform-docs.yaml (included in the repository as an example) contains the structure of this README.md.
  • From the Gruntwork repository, we will use the following hooks:
    - terraform-validate: Automatically run terraform validate on all Terraform code (*.tf files).
    - tflint: Automatically run tflint on all Terraform code (*.tf files).
    - terragrunt-hclfmt: Automatically run terragrunt hclfmt on all Terragrunt configurations.
  • From the Anton Babekno repository, we will use the following hooks:
    - terraform_fmt: Reformat all Terraform configuration files to a canonical format.
    - terraform_checkov: Run Checkov static analysis of terraform templates to spot potential security issues.
    - terraform_tfsec: Run TFSec static analysis of terraform templates to spot potential security issues.
  • From the Pre-Commit repository, we will use some generic hooks; they are self-explanatory and not specific for Terraform or Terragrunt:
    - check-merge-conflict
    - check-yaml
    - detect-private-key
    - end-of-file-fixer
    - trailing-whitespace

We have finally reached the best part, seeing Pre-Commit in action. Run the following command on a terminal from the root of the repository:

$ pre-commit run -a
Pre-Commit in action, manual execution

Changes made by Pre-Commit

As we can see, the Pre-Commit output shows some interesting information. Let’s take a look at it.

List of modified files

With the help of VSCode GUI, we can see all files created or modified by Pre-Commit. As previously mentioned, README.md files were created for every Terraform module:

List of created and modified files

Checkov report

Another hook implemented is Checkov, responsible for analyzing and finding misconfiguration in Infrastructure as Code files. The following is the output of the report:

Check: CKV_AZURE_206: "Ensure that Storage Accounts use replication"
FAILED for resource: azurerm_storage_account.storage

With this error, Checkov is recommending the use of another replication type because LRS isn’t replicated outside a single DC.

Formatting corrections

Automatically format all Terraform and Terragrunt files:

Differences between unformatted and formatted versions of the file

As you can see, the terragrunt-hclfmt hook is responsible for analyzing and correcting any formatting findings. In our case, it modifies the white spacing between the variable and the equal character.

Readme creation

Pre-Commit also creates the README.md file for each of the Terraform modules:

README.md file created from scratch

Code validation

Another incredibly useful check is terraform-validate hook, which helps us reduce the time dedicated to troubleshooting these kinds of mistakes:

Terraform validated output

As we can see in the output, we are using the wrong variable or not declared in the corresponding variable.tf file.

Final Step

So every time that I want to commit some changes, do I need to remember to execute Pre-Commit? No, you don’t. You can execute the following command and forget about manually executing it, as it will integrate with the git commit command:

$ precommit-poc git:(main) ✗ pre-commit install
pre-commit installed at .git/hooks/pre-commit

Conclusion

In this article, we learned how we can use Pre-Commit to significantly reduce the time between each commit and the detection of errors when executing it with other tools like Terragrunt to apply the IaC code. We also outlined the steps to set up Pre-Commit, including installing the necessary tools and how to create a configuration file. Pre-Commit is a fantastic tool that can help us minimize the time we spend triggering Continuous Integration pipelines since it allows the implementation of multiple checks before committing, improving the quality of our code.

References

--

--