tfmake is a tool for automating Terraform with the power of make. It is designed for projects with multiple modules; particularly when dependencies among them are present and an ordered execution is required.
- bash 4+
- yq
- make
- To ensure an ordered execution, all dependencies between modules MUST be explicitly declared.
- A module path SHOULD NOT contain spaces; e.g. use
sample_module
instead ofsample module
.
git clone https://github.com/tfmake/tfmake.git
sudo cp -r tfmake/usr/local/* /usr/local/
sudo chmod +x /usr/local/bin/tfmake
Usage:
tfmake <command> [options]
Core Commands:
context Define the execution context: validate, plan, apply, or destroy.
init Initialize the data directory for Terraform execution.
generate Create a Makefile to orchestrate the Terraform execution.
run Run the generated Makefile for Terraform execution.
Other Commands:
cleanup Cleanup the data directory.
config Configure tfmake settings.
graph Visualize Terraform modules and their dependencies.
output Display output values from a Terraform module.
summary Generate a Markdown summary from Terraform execution logs.
touch Mark modified files to trigger necessary updates.
Shortcut Commands:
validate Execute core commands using the "validate" context.
plan Execute core commands using the "plan" context.
apply Execute core commands using the "apply" context.
destroy Execute core commands using the "destroy" context.
GitHub Commands:
gh-pr-comment Post a comment on a GitHub pull request.
gh-step-summary Append content to GitHub Step Summary.
Global Options:
-h, --help, help Display this help message and exit.
-v, --version Alias for the "version" command.
tfmake is based on the explicit declaration of dependencies between modules. In a Terraform project, this could be inferred from the usage of terraform_remote_state
data resource; although implicit dependencies cases could exist.
The syntax for the .tfmake
file is as follow:
dependencies: [<module>, <module>, ..., <module>]
e.g. if module C depends on A and B, the following declaration is needed:
dependencies:
- A
- B
All dependencies MUST be declared. Omitting some of them based on transitivity is discouraged.
Similar to Terraform, tfmake is composed of multiple commands, each one playing an important role in a predefined sequence.
The core sequence is made up of four commands, as illustrated in the next diagram.
flowchart LR
classDef primary fill:#a3cfbb,stroke:#a3cfbb,color:#136c44;
classDef secondary fill:#fee69b,stroke:#fee69b,color:#987405;
context("tfmake context")
init("tfmake init")
generate("tfmake generate")
run("tfmake run")
context --> init --> generate --> run
context:::primary
init:::primary
generate:::primary
run:::primary
Allows to define the Terraform command to execute: validate
, plan
, apply
, or destroy
.
tfmake context plan
As the name suggests, this command deals with the initialization process, used to discover the Terraform modules, and their dependencies. That information is persisted for further usage.
tfmake init
Sometimes, a requirement arises to exclude some modules inside a project. The option --exclude
supports it, by passing a space separated list of modules.
tfmake init -i "X Y Z"
This command acts like a code generator, using the information gathered by init to create a Makefile
. Each module is added as a target, with their files and dependencies as prerequisites. The tfmake context determines the Terraform command to use as part of the target's recipes.
tfmake generate
What follows is an adapted example for a three-module project plan Makefile
.
all: A B C
A: $(wildcard A/*.tf A/*.tfvars)
terraform -chdir="A" init
terraform -chdir="A" plan
B: $(wildcard B/*.tf B/*.tfvars) A
terraform -chdir="B" init
terraform -chdir="B" plan
C: $(wildcard C/*.tf C/*.tfvars) A B
terraform -chdir="C" init
terraform -chdir="C" plan
When the Makefile
is there, it's possible to use it for running the make utility.
One of the goals of tfmake is to avoid unnecessary executions. If a module (target) files or their dependencies don't change, there is no need to run a validate
, plan
, apply
, or destroy
on it. This behavior, derived from the make
way of working, reduces the execution time and favors cost optimization.
The make program uses the makefile description and the last-modification times of the files to decide which of the files need to be updated.
As mentioned before, a Makefile
is the entrypoint for make
execution. The run command brings some advantages over it, including multiple modes of execution and a proper handling of failures for CI/CD pipelines.
By default (tfmake run
), the command calls make
and runs it with the semantics described above, avoiding unnecessary executions. However, two other modes exist with the options --all
and --dry-run
.
The first one executes Terraform validate
, plan
, apply
, or destroy
for all modules, whereas the second is similar to the default mode, producing a list of modules but without running their recipes.
By default, tfmake
uses terraform
as its infrastructure as code tool. However, it can also be set to use OpenTofu if preferred.
tfmake config --set iactool tofu
The touch command is a wrapper over the corresponding Linux utility, and is used to change the modification time of modules files. This is mandatory in a GitHub Actions workflow after a checkout or could be used in a local environment to force the make
semantics.
For instance, using the modules A, B and C; a change in a .tf
file inside B will cause the execution of it and C in that order.
The command could be executed with the option -f|--files
and a list of space separated files.
tfmake touch -f "A/main.tf B/main.tf"
is equivalent to
tfmake touch -f "A/main.tf" -f "B/main.tf"
In a GitHub Actions workflow use
tj-actions/changed-files
to get the list of changed files.
When the run command is finished, the outputs from Terraform executions for each module are stored for further processing. The summary command produces a report in Markdown format, after joining them all.
When running tfmake
as part of a GitHub Actions workflow, it is possible to use the report to provide feedback in two different ways.
The first is by means of a Pull Request comment, with the help of the following command.
tfmake gh-pr-comment --number "${{ github.event.pull_request.number }}"
The second alternative is based on the GitHub Actions Step Summary feature, with the help of the gh-step-summary command.