tftargets: Extracting Terraform Plan/Apply Target Root Modules from Git Branch Diffs



This content originally appeared on DEV Community and was authored by Ryo TAKAISHI

In Terraform CI/CD, running plan/apply for all modules every time is a waste of time and resources.
For example, if you have dozens of modules, running plan/apply on unchanged modules is inefficient.

To address this, I created tftargets, a tool that outputs a list of modules requiring plan/apply by comparing changes with a specified Git branch.

With tftargets, you can:

  • Run plan only for one environment (e.g., production or staging) when modules are separated per environment.
  • Run plan for multiple root modules when they depend on a changed module.

tftargets

tftargets (https://github.com/takaishi/tftargets) analyzes Git branch diffs to determine, at the module level, whether changes occurred.
It uses terraform-config-inspect (https://github.com/hashicorp/terraform-config-inspect) internally to understand module dependencies and outputs the directories of changed modules as a JSON array.

While tools like dorny/paths-filter can define plan/apply targets, dependency management becomes cumbersome as complexity grows.
tftargets automatically analyzes module dependencies and lists only the necessary root modules.

Installation

tftargets is designed for use in GitHub Actions. You can use it in your workflow as follows:

- uses: takaishi/tftargets@main
  with:
    version: "v0.0.3"

Usage

Suppose you manage your Terraform configuration with the following directory structure.
Environments (staging/production) are separated under env/, and each contains root modules for different purposes.
Root modules call modules under modules/ as needed.

If both app1 and app2 use the ecs module, and you make changes to the ecs module, you will want to run plan/apply for both app1 and app2.

terraform/
├── env/
│   ├── staging/
│   │   ├── network/
│   │   ├── storage/
│   │   ├── app1/
│   │   └── app2/
│   └── production/
│       ├── network/
│       ├── storage/
│       ├── app1/
│       └── app2/
└── modules/
    ├── alb/
    ├── ecs/
    ├── network/
    └── storage/

You can use tftargets (designed for GitHub Actions, but runnable locally) to list target modules by comparing with github.base_ref:

tftargets \
  --base-branch='${{ github.base_ref }}' \
  --base-dir='${{ github.workspace }}' \
  --search-path='terraform/env'

The output will be a JSON array of root module paths:

["/path/to/github/workspace/terraform/env/staging/app1", "/path/to/github/workspace/terraform/env/staging/app2", "/path/to/github/workspace/terraform/env/production/app1", "/path/to/github/workspace/terraform/env/production/app2"]

Extracting only environment names

If you want just the environment names from the tftargets output:

echo $TFTARGETS | jq -c 'map(split("/")[-2]) | unique | sort'
["production","staging"]

You can then use a matrix in subsequent jobs to process each environment:

plan:
  needs: tftargets
  strategy:
    matrix:
      env: ${{ fromJSON(needs.tftargets.outputs.tftargets) }}
  runs-on: arm-ubuntu-latest

Running tftargets in subsequent jobs

Inside a matrix job, run tftargets again to narrow down modules per environment:

tftargets \
  --base-branch='${{ github.base_ref }}' \
  --base-dir='${{ github.workspace }}' \
  --search-path='terraform/env/${{ matrix.env }}'
["/path/to/github/workspace/terraform/env/staging/app1", "/path/to/github/workspace/terraform/env/staging/app2"]

Format the output to generate –queue-include-dir arguments for Terragrunt:

echo $TFTARGETS | jq -rc 'map(split("/")[-1]) | map("--queue-include-dir=" + .) | join(" ")'
--queue-include-dir=app1 --queue-include-dir=app2

Note: Terragrunt (https://terragrunt.gruntwork.io/) is a wrapper for Terraform that simplifies managing many modules.
By defining dependencies in .hcl files, Terragrunt ensures modules are applied in the correct order.

Summary

tftargets efficiently lists root modules for plan/apply based on Git branch diffs and Terraform module dependencies.
It helps minimize unnecessary runs in CI/CD pipelines.

I plan to keep improving and adding features to tftargets. Give it a try and share your feedback.


This content originally appeared on DEV Community and was authored by Ryo TAKAISHI