DEV Community

Cover image for Enhance Terraform/Tofu Automation with GitHub Action
Rishav Dhar
Rishav Dhar

Posted on • Edited on • Originally published at Medium

Enhance Terraform/Tofu Automation with GitHub Action

Provisioning infrastructure-as-code (IaC) in a GitOps framework can feel like walking a tightrope: balancing pipeline security while trying to communicate changes clearly. This blog explores a GitHub Action I maintain—DevSecTop/TF-via-PR—which addresses common pitfalls to plan and apply IaC, including:

  1. Summarize plan changes (with diff)
  2. Reuse plan file (with encryption)
  3. Apply on PR merge (before OR after)

Tip
Throughout this blog, 'TF' is used to reference both Terraform and OpenTofu interchangeably due to first-class support for both tools.


Summarize plan changes (with diff)

While the plan should be transparent, reviewing 1000s of lines of planned changes is simply not feasible. On the other hand, a brief 1-liner like "Plan: 2 to add, 2 to change, 2 to destroy" fails to convey the scope of impact. So why not visualize the summary of changes the same way Git does—with diff syntax highlighting.

Figure: PR comment of TF plan with Figure: PR comment of TF plan with "Diff of changes" section expanded.

Right after the color-coded diff, there’s another collapsible section with the trimmed plan output for a more detailed view. Below that, we’ll find a direct link to the full workflow log—handy for when the output exceeds a PR comment’s character limit. Speaking of workflows, the same output is attached to the job summary, even when used in matrix strategy.

Figure: Workflow log with command-specific job summary.Figure: Workflow log with command-specific job summary.

This is achieved with the following few lines of GitHub Action.

jobs:
  provision:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@4

      - name: Setup TF
        uses: hashicorp/setup-terraform@v3

      - name: Provision TF
        uses: devsectop/tf-via-pr@v12
        with:
          command: plan
          arg-lock: false
          arg-var-file: env/dev.tfvars
          working-directory: stacks
Enter fullscreen mode Exit fullscreen mode

By the way, you may have noticed the oddly-named "terraform[...]tfplan" artifact in the previous screenshot and wondered what it's all about.


Reuse plan file (with encryption)

Too often, when a plan is approved for merge, the IaC pipeline is rerun to apply changes with auto-approve enabled. This can lead to unpredictable results, especially if configuration drift occurs due to changes made outside the workflow. Since both the code and pipeline are hosted on GitHub, we can take advantage of workflow artifacts to store and reuse the plan file between runs.

To ensure the triggered workflow picks up the correct plan file artifact, it needs a uniquely identifiable name which accounts for variables, such as:

  • Tool: either terraform or tofu.
  • PR number: so multiple PR branches can plan simultaneously without over-writing each other.
  • CLI arguments: workspace, working directory, backend-config, var-file, and destroy.

Additionally, we want to avoid the risk of exposing sensitive data by uploading the plan file as-is. Instead, the file should be encrypted with a secret string before upload. Here's how we can add this to the GitHub Action workflow step from before.

- name: Provision TF
  uses: devsectop/tf-via-pr@v12
  with:
    command: plan
    arg-lock: ${{ github.event_name == 'push' }}
    arg-var-file: env/dev.tfvars
    working-directory: stacks
    plan-encrypt: ${{ secrets.PASSPHRASE }}
Enter fullscreen mode Exit fullscreen mode

Tip
For a deeper dive into securing cloud provisioning pipelines, check this blog out.


Speaking of reuse, it’s common for a PR to accumulate several commits before it’s ready to merge. In this case, plan updates can be rendered in one of two ways.

  • Update (default): the existing PR comment is updated in place, complete with a revision history to track changes over time.
  • Recreate: the existing PR comment is deleted and replaced with a new one after each commit.

Figure: PR comment revision history comparing plan and apply outputs.Figure: PR comment revision history comparing plan and apply outputs.

By now, you’ll have noticed that we’re applying changes with the same GitHub Action—the final piece of the puzzle pipeline.


Apply on PR merge (before OR after)

Whether you decide to apply IaC changes before or after merging, the workflow adapts to fit your needs. By using unique identifiers, we can retrieve the relevant plan file even if the PR branch has been pushed to the default branch, outside of 'pull_request' context. Here’s a complete workflow example to illustrate.

on:
  pull_request:
  push:
    branches: [main]

jobs:
  provision:
    runs-on: ubuntu-latest

    permissions:
      actions: read        # Required to identify workflow run.
      checks: write        # Required to add status summary.
      contents: read       # Required to checkout repository.
      pull-requests: write # Required to add comment and label.

    steps:
      - name: Checkout repository
        uses: actions/checkout@4

      - name: Setup TF
        uses: hashicorp/setup-terraform@v3

      # Only plan by default, or apply with lock on merge.
      - name: Provision TF
        uses: devsectop/tf-via-pr@v12
        with:
          command: ${{ github.event_name == 'push' && 'apply' || 'plan' }}
          arg-lock: ${{ github.event_name == 'push' }}
          arg-var-file: env/dev.tfvars
          working-directory: stacks
          plan-encrypt: ${{ secrets.PASSPHRASE }}
Enter fullscreen mode Exit fullscreen mode

We're not limited to just these workflow triggers; it's compatible with 'merge_group' using a merge queue to filter out failed apply attempts. Other triggers, like 'cron' (for scheduled drift checks) and 'workflow_dispatch' (for manual checks), are also supported, with their plan file shared between workflows.


Bonus Extras

While this blog has primarily focused on planning and applying changes, we can also perform 'fmt' and 'validate' checks, along with 'workspace' selection. In fact, we can pass the full range of TF options and flags using the 'arg-' prefix, such as 'arg-auto-approve: true', 'arg-destroy: true', 'arg-workspace: dev', and 'arg-parallelism: 20'.

For more complex workflows, detailed 'exitcode' and 'identifier' outputs are provided to help with decision chains or to integrate with linting and security scans. You can find the full list of parameters documented in the Readme.

GitHub logo DevSecTop / TF-via-PR

Plan and apply Terraform/OpenTofu via PR automation, using best practices for secure and scalable IaC workflows.

Terraform Compatible OpenTofu Compatible * GitHub license GitHub release tag * GitHub repository stargazers

Terraform/OpenTofu via Pull Request (TF-via-PR)













What does it do?



Who is it for?



  • Plan and apply changes with CLI arguments and encrypted plan file to avoid configuration drift.
  • Outline diff changes within updated PR comment and matrix-friendly workflow summary, complete with log.


  • DevOps and Platform engineers wanting to empower their teams to self-service scalably.
  • Maintainers looking to secure their pipeline without the overhead of containers or VMs.



PR comment of plan output with "Diff of changes" section expanded.


Usage

How to get started quickly?

on
  pull_request:
  push:
    branches: [main]

jobs:
  provision:
    runs-on: ubuntu-latest

    permissions:
      actions: read        # Required to identify workflow run.
      checks: write        # Required to add status summary.
      contents: read       # Required to checkout repository.
      pull-requests: write # Required to add comment and label.

    steps:
      - uses: actions/checkout@4
      - uses: hashicorp/setup-terraform@v3
Enter fullscreen mode Exit fullscreen mode

All forms of contribution are welcome and appreciated for fostering open-source projects. Please feel free to open a discussion to share your ideas, or become a stargazer if you find this project useful.

Whether it's interpolating dynamic backends, bulk-provisioning environments simultaneously, or triggering actions through labels and comments, this workflow offers plenty of flexibility. Is there a specific setup you'd like us to dive into next?

Top comments (0)