Terraform Canonical Formatting Automation via GitHub Actions

The implementation of Infrastructure as Code (IaC) requires not only functional correctness but also strict adherence to stylistic standards to ensure maintainability and readability across large engineering teams. Within the Terraform ecosystem, the terraform fmt command serves as the authoritative tool for rewriting Terraform configuration files into a canonical format. When integrated into a Continuous Integration (CI) pipeline using GitHub Actions, this process transforms from a manual developer task into an automated quality gate. This automation prevents "style drift" and eliminates trivial diffs in pull requests that are caused solely by whitespace or indentation changes, thereby allowing reviewers to focus on actual architectural modifications.

The integration of formatting checks into GitHub Actions typically follows two primary philosophies: the "Check and Fail" approach and the "Auto-Fix and Commit" approach. The former treats formatting as a linting requirement where the build fails if the code is not canonical, forcing the developer to fix the issue locally. The latter employs specialized actions to automatically reformat the code and push the changes back to the repository via a pull request. By leveraging the dflook/terraform-fmt suite and native GitHub Action runners, organizations can ensure that every line of HCL (HashiCorp Configuration Language) adheres to the industry-standard canonical format before it ever reaches a production environment.

Technical Analysis of the dflook Terraform Formatting Suite

The dflook suite provides a specialized set of actions designed specifically for Terraform maintenance. Two distinct behaviors are offered: one for checking compliance and one for active reformatting.

The terraform-fmt-check Action

This specific action is engineered to validate that all files within a designated Terraform configuration directory adhere to the canonical format without modifying the files themselves. It executes the terraform fmt command with a check flag to verify compliance.

If the action detects any file that does not meet the canonical standard, it triggers a failure in the GitHub check. This results in a failed job status for the entire workflow, which can be configured to block the merging of a pull request. This creates a hard requirement for developers to run terraform fmt locally before pushing their code.

The following table details the configuration parameters available for the terraform-fmt-check action:

Parameter Type Optional Default Description
path string Yes Action workspace The specific directory containing the Terraform files to be checked.
workspace string Yes default The Terraform workspace used for version discovery if not otherwise specified.
backend_config string Yes N/A A list of backend configuration values, provided one per line, used for version discovery.
backend_config_file string Yes N/A A list of backend configuration files used to discover the Terraform version.
failure-reason string Yes N/A Set to check-failed if the job fails specifically due to a formatting error.

The terraform-fmt Action for Reformatting

Unlike the check action, the terraform-fmt action utilizes the terraform fmt -recursive command. This does not simply report errors; it actively reformats the files within the target directory into the canonical format. This is particularly powerful when combined with other actions that can commit these changes back to the branch.

This action shares several configuration parameters with the check action, such as path, workspace, backend_config, and backend_config_file. However, it introduces specific parameters for binary management and cloud connectivity.

The GITHUB_DOT_COM_TOKEN is a critical parameter for users operating on GitHub Enterprise instances. Because the action may need to download OpenTofu binaries from GitHub.com, this token prevents rate-limiting issues that occur with unauthenticated requests.

Furthermore, the TERRAFORM_CLOUD_TOKENS parameter allows the action to interact with cloud backends to detect the required Terraform version. These tokens must be formatted as <host>=<token>, and multiple entries can be provided, one per line.

Advanced Configuration and Version Discovery

A recurring challenge in Terraform automation is ensuring the CI runner uses the exact version of Terraform specified in the project's configuration. The dflook actions integrate with dflook/terraform-version to automate this discovery process.

Backend Configuration for Versioning

The use of backend_config and backend_config_file is not for the purpose of initializing the backend, but specifically for discovering the Terraform version to use. This is essential when the version is not explicitly defined in the environment.

For the backend_config parameter, a user might pass a secret token as follows:

yaml with: backend_config: token=${{ secrets.BACKEND_TOKEN }}

For the backend_config_file, the paths must be relative to the GitHub Actions workspace:

yaml with: backend_config_file: prod.backend.tfvars

Cloud Token Management

To fetch required modules from a registry or interact with a remote backend during the version discovery phase, the TERRAFORM_CLOUD_TOKENS environment variable is used. This can be configured in the workflow as follows:

yaml env: TERRAFORM_CLOUD_TOKENS: | app.terraform.io=${{ secrets.TF_CLOUD_TOKEN }} terraform.example.com=${{ secrets.TF_REGISTRY_TOKEN }}

Solving Directory Displacement and Configuration Errors

A common failure point in GitHub Actions occurs when Terraform configuration files are not located in the root directory of the repository. In such cases, the terraform plan or terraform fmt commands will fail with the error: Error: No configuration files.

The Root Cause of Configuration Failures

When a workflow executes a command like terraform init or terraform plan, the Terraform binary expects to find .tf files in the current working directory. If the files are stored in a subdirectory (e.g., /terraform or /infra), the process will exit with code 1, stating that planning without configuration would mark everything for destruction.

The working-directory Solution

To resolve this, the working-directory value must be added to the job definition within the GitHub Actions YAML. This ensures that all subsequent run commands are executed within the context of the folder containing the configuration.

The correct implementation structure is:

yaml jobs: terraform: defaults: run: working-directory: $DIR_OF_CHOICE

By defining the working-directory at the job level, the developer avoids having to specify the path in every individual step, ensuring that terraform init, terraform fmt -check, and terraform plan all target the correct set of files.

Implementation Patterns for Formatting Workflows

Depending on the organizational goal, formatting can be handled through a "Linting" gate or an "Auto-Fix" pipeline.

Pattern 1: The Linting Gate (PR Validation)

In this pattern, the workflow is triggered by a pull_request event. The goal is to notify the developer that their code is not formatted correctly.

Example implementation for a linting job:

yaml name: Plan / Test On PR on: pull_request: branches: - main jobs: lint: name: Lint runs-on: ubuntu-20.04 steps: - name: Check out code uses: actions/checkout@v2 - name: Setup Terraform uses: hashicorp/setup-terraform@v1 with: terraform_version: 1.0.9 - name: Run terraform fmt check run: terraform fmt -check -diff -recursive ./terraform

In this configuration, the -check flag ensures the command returns a non-zero exit code if the format is incorrect, and -diff provides the specific changes needed to achieve canonical formatting in the logs.

Pattern 2: The Auto-Fix Pipeline (Automated PRs)

This pattern is designed for efficiency, where the CI system fixes the formatting and creates a new pull request with the corrected code. This is achieved by combining dflook/terraform-fmt with the peter-evans/create-pull-request action.

Detailed workflow for automated formatting:

yaml name: Fix Terraform file formatting on: push: branches: - main jobs: format: runs-on: ubuntu-latest name: Check Terraform file are formatted correctly steps: - name: Checkout uses: actions/checkout@v4 - name: terraform fmt uses: dflook/terraform-fmt@v2 with: path: my-terraform-config - name: Create Pull Request uses: peter-evans/create-pull-request@v5 with: commit-message: terraform fmt title: Reformat terraform files body: Update Terraform files to canonical format using `terraform fmt` branch: automated-terraform-fmt

This workflow triggers on every push to the main branch. If the terraform-fmt action modifies any files, the subsequent step creates a PR titled "Reformat terraform files" on a branch named automated-terraform-fmt.

Infrastructure of GitHub Actions for Terraform

To understand how these formatting actions fit into the larger ecosystem, it is necessary to analyze the hierarchy of GitHub Actions.

Workflows, Jobs, and Steps

A GitHub Action implementation is structured as follows:

  • Workflows: These are the top-level orchestrators triggered by events such as push or pull_request.
  • Jobs: These are the units of execution. Each job creates a virtual machine (runner), such as ubuntu-latest.
  • Steps: These are the individual tasks executed within a job. Steps can share files and environment variables because they reside on the same VM.

Matrix Strategies and Specialized Tooling

For complex environments, a matrix strategy can be employed to run the same formatting and plan jobs across multiple versions of Terraform or different operating systems in parallel. Furthermore, while basic GitHub Actions are sufficient for simple deployments, advanced users may integrate specialized tools like HCP Terraform (formerly Terraform Cloud) or Terramate to handle state management and orchestration.

The following table compares the native terraform fmt command versus the dflook action wrappers:

Feature Native terraform fmt dflook/terraform-fmt Action
Execution Manual or via run step Specialized Action step
Version Management Manual via setup-terraform Automated via terraform-version
Error Reporting Standard shell output GitHub Check integration
OpenTofu Support Manual installation Integrated via GITHUB_DOT_COM_TOKEN
Recursive Support Requires -recursive flag Built-in to the action logic

Conclusion: Strategic Analysis of Formatting Automation

The automation of terraform fmt via GitHub Actions is not merely a matter of aesthetic preference but a critical component of a robust DevOps lifecycle. By implementing a mandatory formatting check, organizations eliminate "noisy" diffs, which reduces the cognitive load on reviewers and accelerates the merge process. The transition from using the native terraform fmt -check command to utilizing specialized actions like those provided by dflook allows for more sophisticated version discovery and better integration with GitHub's native check system.

The most effective strategy is a hybrid approach: employing a "Lint" job on pull requests to educate developers on canonical standards, while maintaining an automated "Fix" workflow that ensures the main branch remains pristine. The use of working-directory is a non-negotiable requirement for any non-root configuration, and the strategic use of TERRAFORM_CLOUD_TOKENS ensures that versioning remains consistent across the entire infrastructure pipeline. Ultimately, these practices transform the codebase into a predictable, standardized asset that is resilient to the inconsistencies of manual editing.

Sources

  1. dflook/terraform-fmt-check
  2. terraform-fmt GitHub Marketplace
  3. HashiCorp Discuss - Non-root directory configuration
  4. Dev.to - GitHub Actions to Deploy Terraform Code
  5. TestDouble - GitHub Actions Terraform Automation

Related Posts