Quickstart β
This tutorial aims to introduce you to the basic concepts of Terramate, and demonstrate how a filesystem-oriented code generator can improve the management of your Terraform code efficiently.
To follow this tutorial, you will need local installations of Terramate, Terraform, and Git.
Note that you won't need a cloud account, as we'll be using the local_file
resource to create a static site that demonstrates Terramate's fundamental principles.
We launched a Terramate Discord Server in case you have any additional questions or issues running the examples in this guide.
Set up Terramate β
If you havenβt installed Terramate yet, please refer to the installation section for various options.
To start using Terramate, there are two possible ways of configuring a Terramate project. If you invoke Terramate inside a Git repository, Terramate will automatically assume the top-level of your repository as the project root. If you want to use Terramate in a directory that isn't a Git repository, you must configure the project root by creating a terramate.tm.hcl
configuration at the top level or any sub-directory in your Terramate project.
We will start working on a plain Terramate project without Git integration and add Git later to the project only. To begin, create a new directory that contains a file named terramate.tm.hcl
with the following content:
# file: terramate.tm.hcl
terramate {
config {
}
}
Next, navigate "cd"
to the new directory and run:
$ terramate create mysite
This creates a Terramate Stack, which is represented as a directory called mysite
containing a file named stack.tm.hcl
containing a stack {}
block:
# file: stack.tm.hcl
stack {
name = "mysite"
description = "mysite"
id = "3b08c008-f0b2-4d01-8021-4c523199123e"
}
Terramate recognizes files with the .tm
or .tm.hcl
extension, and any file containing a stack {}
block is considered a stack. A stack is simply a directory where Terramate generates Terraform files.
Note: You can manually create the
stack.tm.hcl
file without an ID or by running theterramate create
command.
However, generating unique IDs for stacks is recommended since they enable direct stack referencing instead of relative paths, making refactoring difficult.
Run terramate list
to see the mysite/
directory listed as a stack. If it doesn't appear, ensure you're in the correct directory, as Terramate uses the current working directory as the context for executing all commands.
At this point, you have created a stack, but it does not perform any actions. Although you can generate Terraform code and run it without errors, it will not produce any results.
terramate generate
Nothing to do, generated code is up to date
Terramate Code Generation β
Letβs make it do something. Append the following to the mysite/stack.tm.hcl
:
# file: mysite/stack.tm.hcl
...
generate_hcl "mysite.tf" {
content {
resource "local_file" "mysite" {
filename = "/tmp/tfmysite/index.html"
content = <<-EOF
<html>
<title>My Website</title>
</html>
EOF
}
}
}
The code provided demonstrates how to generate an HCL file named mysite.tf
that contains Terraform code for a local_file
resource. To create the mysite.tf
file, run the terramate generate
command. One of the key advantages of the Terramate is that it doesn't require any wrappers; it seamlessly creates native Terraform code.
Simply navigate to the directory, and execute terraform init
and terraform apply
to generate a local file in /tmp/tfmysite/index.html
with the HTML code.
Generate Dynamic Code with Terramate Globals β
To create dynamic content, Terramate uses variables called Globals. These variables can be defined in any Terramate file within a globals {}
block. Each directory inherits all Globals from its parent directory, and any Globals with the same name will be overwritten. There are no complicated precedence rules. Subdirectories will overwrite parent directory Globals if they share the same name.
First, define a global variable called title
in the root directory. Create a file named globals.tm.hcl
and add the following content:
# file: globals.tm.hcl
globals {
title = "My Website"
}
The name globals.tm.hcl
is not required, and the content could be included in an existing terramate.tm.hcl
file. All Terramate files are merged during runtime, similar to how Terraform merges .tf
files. This simplicity provides great flexibility.
Next, update the mysite/stack.tm.hcl
file by replacing the <title>
with <title>${global.title}</title>
. Running terramate generate
will not change anything, but the title now references a global variable.
With a dynamic title in place, you can create separate environments for development and production.
Modularising theΒ code β
In this section, we will move our mysite
stack into a "module" for reusability. A "module" here means "code that is not a stack and is intended for reuse" - unrelated to Terraform modules.
To do this, follow these steps:
- Run the following commands in the project root:
mkdir -p modules/mysite
mv mysite/stack.tm.hcl modules/mysite/mysite.tm.hcl
rm -r mysite
- In
modules/mysite/mysite.tm.hcl
, remove thestack {}
block since code generation should no longer occur directly in the modules directory. This file should now only contain thegenerate_hcl
block.
When running terramate list
, no stacks should appear, as there are no Terramate files with a stack{}
block within the file tree.
$ cat modules/mysite/mysite.tm.hcl
generate_hcl "mysite.tf" {
content {
resource "local_file" "mysite" {
filename = "/tmp/tfmysite/index.html"
content = <<-EOF
<html>
<title>${global.title}</title>
</html>
EOF
}
}
}
Next, create production (prod) and development (dev) stacks in the root directory:
terramate create dev/mysite
terramate create prod/mysite
Your file structure should now look like this:
dev/
mysite/
stack.tm.hcl
modules/
mysite/
mysite.tm.hcl
prod/
mysite/
stack.tm.hcl
globals.tm.hcl
terramate.tm.hcl
We want to customize mysite
stack under the dev
directory for a development environment and the same for prod. To achieve this, use additional globals files located in our environment subdirectories, named dev/dev.tm.hcl
and prod/prod.tm.hcl
:
# file: dev/dev.tm.hcl
globals {
env = "dev"
}
# file: prod/prod.tm.hcl
globals {
env = "prod"
}
With this change, any stack under the prod directory will inherit a global.env == "prod"
(unless overwritten or explicitly unset), and the same applies to dev
. Now we want to import mysite
code into the stack in each environment. In <env>/mysite/stack.tm.hcl
insert the following:
# file: <env>/mysite/stack.tm.hcl
import {
source = "/modules/mysite/mysite.tm.hcl"
}
Update the output filename of the local_file resource in /modules/mysite/stack.tm.hcl
to avoid overwriting between dev and prod. Use the Terramate metadata ${terramate.stack.path.relative}
in the path name:
# in: modules/mysite/mysite.tm.hcl
generate_hcl "mysite.tf" {
content {
resource "local_file" "mysite" {
filename = "/tmp/tfmysite/${terramate.stack.path.relative}/index.html"
...
To execute Terraform, navigate "cd"
to prod/mysite
and dev/mysite
and run the necessary Terraform commands.
However, to avoid manual execution and potential errors, run terramate run terraform init
in the project root directory.
$ terramate run terraform init
2023-04-06T14:32:47+01:00 ERR outdated code found action=checkOutdatedGeneratedCode() filename=dev/mysite/mysite.tf
2023-04-06T14:32:47+01:00 ERR outdated code found action=checkOutdatedGeneratedCode() filename=prod/mysite/mysite.tf
2023-04-06T14:32:47+01:00 FTL please run: 'terramate generate' to update generated code error="outdated generated code detected" action=checkOutdatedGeneratedCode()
Whoops, we have errors!
We should have run terramate generate
before terramate run
. Thankfully, Terramate detected that the generated code needed to be updated and prevented us from running Terraform with outdated code.
To fix this, run terramate generate
, followed by terramate run terraform init
again. Terraform should now initialize sequentially in dev and prod. By default, terramate run executes in filesystem order, but Terramate can control the order of execution if needed.
Now run:
$ terramate run terraform apply
After approval, the rendered HTML files should be located at /tmp/tfmysite/<env>/mysite/index.html
Terramate Orchestration with Change Detection β
To conclude this introduction, let's explore globals precedence and one of Terramate's most powerful features: change detection. Change detection is designed for CI-CD pipelines and requires a working Git repository with a remote. In your project's root, run:
git init -b main
git add *
git commit -m 'initial commit'
Next, set up a temporary local git remote to push to:
fake_github=$(mktemp -d)
git init -b main "${fake_github}" --bare
git remote add origin "${fake_github}"
git push --set-upstream origin main
In your project directory, git remote -v
should now look something like this:
$ git remote -v
origin /var/folders/dt/z_q3n2cs6r18364fhpbzzzmr0000gn/T/tmp.4ndijOue (fetch)
origin /var/folders/dt/z_q3n2cs6r18364fhpbzzzmr0000gn/T/tmp.4ndijOue (push)
Imagine we need to change the title for users in the dev
environment. To demonstrate change management, let's create a new branch
git checkout -b change-dev-title
We now want to overwrite the mysite
title only in dev
. Currently, global.title
is set in the root globals.tm.hcl
. Globals are inherited through the filesystem traversal, so we can overwrite global.title
in any Terramate file in any parent of the stack (either dev/
or dev/mysite/
). Add it to dev/dev.tm.hcl
:
globals {
env = "dev"
title = "THIS IS DEV"
}
To view how globals are evaluated in each stack, run the experimental command terramate experimental globals
:
$ terramate experimental globals
stack "/dev/mysite":
env = "dev"
title = "THIS IS DEV"
stack "/prod/mysite":
env = "prod"
title = "My Website"
Run terramate generate.
You should see that it modified dev/mysite/mysite.tf
:
$ terramate generate
Code generation report
Successes:
- /dev/mysite
[~] mysite.tf
Hint: '+', '~' and '-' mean the file was created, changed and deleted, respectively.
Commit the changed files with git commit -am 'changed dev title'
, then run terramate list --changed
. This command displays stacks with outstanding changes compared to the main branch:
$ terramate list --changed
dev/mysite
Now, if you execute terramate run --changed terraform apply
, it will apply the changes only to the affected stacks.
Conclusion β
We hope this tutorial has helped you grasp the basics of Terramate, and demonstrated how its simple code generation model works based on the filesystem hierarchy, and how it can assist you in organizing your codebase and maintaining its DRYness without any complications.
Terramate is designed to be flexible, and there is a wealth of features to explore. Its power lies in its simplicity, enabling seamless integration with your workflow without requiring you to invest time in learning another API, or complex tooling that further distances you from the native Terraform code you've already built.
If Terramate doesn't meet your current needs, no worries: simply remove all the *.tm{,.hcl}
files, and you're back to using plain Terraform! If you have questions or feature requests regarding Terramate, we encourage you to join our Discord Community or create an issue in the Github repository.