Wed Feb 21 2024

Validate Terragrunt Config with GitHub Workflows

I wrote up a simple workflow for validating my Terragrunt configuration using the terragrunt validate command and GitHub workflows.

What you need to know for this post

  1. Basic familiarity with Terraform
  2. Basic familiarity with Terragrunt
  3. Basic familiarity with GitHub repos
  4. Basic familiarity with GitHub Actions

A brief summary of Terragrunt

Terragrunt is a thin wrapper by Gruntwork for Terraform, which is an Infrastructure-as-Code solution from HashiCorp.

Terragrunt makes managing complex, large Terraform deployments easier than through Terraform alone.

Why validate the Terragrunt configuration?

Validating the Terragrunt configuration should highlight errors in the configuration files that are introduced as changes are made.

When you run terragrunt validate, Terragrunt will download the relevant Terraform module, prepare the working directory for Terraform, and run the terraform validate command.

Using this command has consistently identified typos and errors in my Terragrunt configuration in my experience.

Example validation GitHub workflow definition

name: validate
on:
  workflow_dispatch:
env:
  TERRAGRUNT_TERRAFORM_DEPLOY_KEY: ${{ secrets.TERRAGRUNT_TERRAFORM_DEPLOY_KEY }}
jobs:
  validate_module:
    runs-on: [self-hosted, Linux, X64, terragrunt]
    strategy:
      matrix:
        module_name: [maas, maas_machines, maas_networking, vbmc, virtual_infra, virtual_networking]
    steps:
      - name: Add Deploy Key
        run: echo "$TERRAGRUNT_TERRAFORM_DEPLOY_KEY" > id_rsa
        working-directory: /home/ubuntu/.ssh/
      - name: Set key perms
        run: chmod 0600 id_rsa
        working-directory: /home/ubuntu/.ssh/
      - name: Checkout Terragrunt config
        uses: actions/checkout@v4
      - name: Run Terragrunt validate
        run: terragrunt validate
        working-directory: ./${{ matrix.module_name }}

I used this workflow definition to automate validating my Terragrunt configuration. This example uses a specially prepared, self-hosted GitHub runner with Terraform and Terragrunt installed running Ubuntu. In the future, I will add steps for installing Terraform and Terragrunt using snaps.

The workflow definition is written using YAML formatting.

This file goes in the .github/workflows folder in a repo that will use GitHub Actions and this workflow. I put the workflow in a file called validate.yaml. Once you have workflow files in the .github/workflows folder you will be able to see the workflows under the Actions page in your GitHub repo.

The workflow’s triggers

GitHub workflows happen based on triggers you define in the on block in your GitHub workflow file.

This workflow example is manually triggered instead of triggering on an event, like a commit or a pull request. GitHub workflows can be manually triggered through the GitHub website if use the workflow_dispatch key in your on configuration. Manually triggering your GitHub workflow is great for development and testing purposes.

The workflow’s environment variables

The workflow also sets one environment variable - TERRAGRUNT_TERRAFORM_DEPLOY_KEY - from a repository secret that is also named TERRAGRUNT_TERRAFORM_DEPLOY_KEY. In order to validate my Terragrunt configuration, I have to pull code from a separate private GitHub repository containing my Terraform modules. I am using deploy keys in GitHub to give read-only access to the private GitHub repository. Deploy keys use public and private SSH keys the same as SSH does. I generated a new public/private SSH key pair specifically for this workflow, and I saved the private key as a repository secret in my Terragrunt configuration. I registered the public key in my private Terraform module repository as a deploy key.

I chose to use deploy keys for this purpose instead of access tokens because I did not want to worry about renewing my access tokens and because I did not want to have to use a larger scope of privileges than reading the private repo. They seemed like a good fit for my needs, and I am comfortable with the security tradeoffs I am making for this particular use case.

The workflow jobs

This workflow has a single job template, but it uses a matrix strategy to dynamically create multiple jobs based on the input in the matrix (in this case, the names of Terraform module folders in my Terragrunt config repo).

Runs-on

The runs-on key defines an array of labels for the GitHub runner that should run the job. Using labels and runs-on makes sure this job runs on the correct GitHub runner environment. In this case, I add the terragrunt label to make sure I run on a specially prepared, self-hosted GitHub runner I have registered in my repo with the labels “self-hosted”, “Linux”, “X64”, and “terragrunt”. The jobs will not run on any GitHub runner that does not have these four labels.

Strategy and matrix

The strategy and matrix keys define tell GitHub to create jobs from this template using the variables defined under the matrix. This type of configuration is great for when you execute more or less the same operation with slight adjustments. In my case, I am providing the names of the Terraform modules for the Terragrunt configuration I would like to validate. The information you define in the matrix is available for you to use later in your job’s template.

Steps

The steps are the actual operations that the GitHub runner executes when a job runs. Each step can have one or more attributes defining the behavior and configuration of the command that is executed during the step. Generally at a minimum, a step must define a uses key or a runs key, but GitHub workflows have more configuration options for steps available.This example workflow has four steps.

The first step adds the deploy key to the runner. The runner will echo the contents of the environment variable TERRAGRUNT_TERRAFORM_DEPLOY_KEY into a file called id_rsa inside of the /home/ubuntu/.ssh/ directory.

The second step changes the permissions of the new SSH private key file to 0600, which restricts read and write access to only the user and group who created the key. In this case, both are ubuntu, which is the user running the self-hosted GitHub runner agent.

The third step checks out the contents of my Terragrunt configuration repo using the official GitHub Actions checkout. This action copies the GitHub repo containing your workflow into your GitHub runner so your workflow can access it. This is similar to running git clone to download your repo into your GitHub runner environment.

The fourth step runs terragrunt validate to validate the Terragrunt configuration for the variable input from the matrix. The working-directory key in the step makes the GitHub runner run the command from the specified directory. I have structured my Terragrunt configuration to have individual configuration files in subfolders. Each of the jobs in this workflow will run terragrunt validate in their respective sub folders. For example, the job with maas as the value for module_name will execute terragrunt validate in a folder at ./maas.

If everything in the given folder validates successfully, Terragrunt will report a success and exit with a status code of 0. The exit status code 0 tells GitHub that the job was a success, and GitHub will show a successful result in the GitHub website. If the validation fails, GitHub will mark the job as failed. With the current configuration, GitHub will fail the entire workflow and cancel any remaining jobs if one job fails. You can configure GitHub to continue running jobs if jobs fail during the workflow.

That’s it!

This workflow configuration provides basic automated validation for Terragrunt configuration and Terraform modules.

Next steps

This workflow has several areas for improvement. One important area of improvement is setting the workflow to trigger on pull requests and commits to automatically validate changes submitted to the Terragrunt configuration repo. This will help identify bugs and errors sooner and more effectively than manual testing.

Another improvement would be installing Terraform and Terragrunt as part of the workflow instead of relying on a specially prepared, self-hosted GitHub runner. Installing Terraform on Ubuntu is simple enough using the Terraform snap. There is not an up-to-date Terragrunt snap unfortunately. I plan to publish one soon, though. I will update the workflow to install Terraform and Terragrunt once I have snaps for both tools.

Sum it all up

GitHub Workflows provide a good way to quickly set up automated testing and exercising for your code. The example worfklow I covered is a real world use of GitHub Workflows to validate my Terragrunt configuration against my Terraform modules. I hope this configuration will help me identify errors that I would have missed otherwise.