Terragrunt allows you to use built-in functions anywhere in terragrunt.hcl
, just like Terraform! The functions currently available are:
All Terraform built-in functions are supported in Terragrunt config files:
terraform {
source = "../modules/${basename(get_terragrunt_dir())}"
}
remote_state {
backend = "s3"
config = {
bucket = trimspace(" my-terraform-bucket ")
region = join("-", ["us", "east", "1"])
key = format("%s/terraform.tfstate", path_relative_to_include())
}
}
Note: Any file*
functions (file
, fileexists
, filebase64
, etc) are relative to the directory containing the terragrunt.hcl
file they’re used in.
Given the following structure:
└── terragrunt
└── common.tfvars
├── assets
| └── mysql
| └── assets.txt
└── terragrunt.hcl
Then assets.txt
could be read with the following function call:
file("assets/mysql/assets.txt")
find_in_parent_folders()
searches up the directory tree from the current terragrunt.hcl
file and returns the absolute path to the first terragrunt.hcl
in a parent folder or exit with an error if no such file is found. This is primarily useful in an include
block to automatically find the path to a parent terragrunt.hcl
file:
include {
path = find_in_parent_folders()
}
The function takes an optional name
parameter that allows you to specify a different filename to search for:
include {
path = find_in_parent_folders("some-other-file-name.hcl")
}
You can also pass an optional second fallback
parameter which causes the function to return the fallback value (instead of exiting with an error) if the file in the name
parameter cannot be found:
include {
path = find_in_parent_folders("some-other-file-name.hcl", "fallback.hcl")
}
path_relative_to_include()
returns the relative path between the current terragrunt.hcl
file and the path
specified in its include
block. For example, consider the following folder structure:
├── terragrunt.hcl
└── prod
└── mysql
└── terragrunt.hcl
└── stage
└── mysql
└── terragrunt.hcl
Imagine prod/mysql/terragrunt.hcl
and stage/mysql/terragrunt.hcl
include all settings from the root terragrunt.hcl
file:
include {
path = find_in_parent_folders()
}
The root terragrunt.hcl
can use the path_relative_to_include()
in its remote_state
configuration to ensure each child stores its remote state at a different key
:
remote_state {
backend = "s3"
config = {
bucket = "my-terraform-bucket"
region = "us-east-1"
key = "${path_relative_to_include()}/terraform.tfstate"
}
}
The resulting key
will be prod/mysql/terraform.tfstate
for the prod mysql
module and stage/mysql/terraform.tfstate
for the stage mysql
module.
path_relative_from_include()
returns the relative path between the path
specified in its include
block and the current terragrunt.hcl
file (it is the counterpart of path_relative_to_include()
). For example, consider the following folder structure:
├── sources
| ├── mysql
| | └── \*.tf
| └── secrets
| └── mysql
| └── \*.tf
└── terragrunt
└── common.tfvars
├── mysql
| └── terragrunt.hcl
├── secrets
| └── mysql
| └── terragrunt.hcl
└── terragrunt.hcl
Imagine terragrunt/mysql/terragrunt.hcl
and terragrunt/secrets/mysql/terragrunt.hcl
include all settings from the root terragrunt.hcl
file:
include {
path = find_in_parent_folders()
}
The root terragrunt.hcl
can use the path_relative_from_include()
in combination with path_relative_to_include()
in its source
configuration to retrieve the relative terraform source code from the terragrunt configuration file:
terraform {
source = "${path_relative_from_include()}/../sources//${path_relative_to_include()}"
}
The resulting source
will be ../../sources//mysql
for mysql
module and ../../../sources//secrets/mysql
for secrets/mysql
module.
Another use case would be to add extra argument to include the common.tfvars
file for all subdirectories:
terraform {
extra_arguments "common_var" {
commands = [
"apply",
"plan",
"import",
"push",
"refresh"
]
arguments = [
"-var-file=${get_terragrunt_dir()}/${path_relative_from_include()}/common.tfvars",
]
}
}
This allows proper retrieval of the common.tfvars
from whatever the level of subdirectories we have.
get_env(NAME)
return the value of variable named NAME
or throws exceptions if that variable is not set. Example:
remote_state {
backend = "s3"
config = {
bucket = get_env("BUCKET")
}
}
get_env(NAME, DEFAULT)
returns the value of the environment variable named NAME
or DEFAULT
if that environment variable is not set. Example:
remote_state {
backend = "s3"
config = {
bucket = get_env("BUCKET", "my-terraform-bucket")
}
}
Note that Terraform will read environment variables that start with the prefix TF_VAR_
, so one way to share a variable named foo
between Terraform and Terragrunt is to set its value as the environment variable TF_VAR_foo
and to read that value in using this get_env()
built-in function.
get_platform()
returns the current Operating System. Example:
inputs = {
platform = get_platform()
}
This function can also be used in a comparison to evaluate what to do based on the current operating system. Example:
output "platform" {
value = var.platform == "darwin" ? "(value for MacOS)" : "(value for other OS's)"
}
Some of the returned values can be:
darwin
freebsd
linux
windows
get_terragrunt_dir()
returns the directory where the Terragrunt configuration file (by default terragrunt.hcl
) lives. This is useful when you need to use relative paths with remote Terraform configurations and you want those paths relative to your Terragrunt configuration file and not relative to the temporary directory where Terragrunt downloads the code.
For example, imagine you have the following file structure:
/terraform-code
├── common.tfvars
├── frontend-app
│ └── terragrunt.hcl
Inside of /terraform-code/frontend-app/terragrunt.hcl
you might try to write code that looks like this:
terraform {
source = "git::git@github.com:foo/modules.git//frontend-app?ref=v0.0.3"
extra_arguments "custom_vars" {
commands = [
"apply",
"plan",
"import",
"push",
"refresh"
]
arguments = [
"-var-file=../common.tfvars" # Note: This relative path will NOT work correctly!
]
}
}
Note how the source
parameter is set, so Terragrunt will download the frontend-app
code from the modules
repo into a temporary folder and run terraform
in that temporary folder. Note also that there is an extra_arguments
block that is trying to allow the frontend-app
to read some shared variables from a common.tfvars
file. Unfortunately, the relative path (../common.tfvars
) won’t work, as it will be relative to the temporary folder! Moreover, you can’t use an absolute path, or the code won’t work on any of your teammates’ computers.
To make the relative path work, you need to use get_terragrunt_dir()
to combine the path with the folder where the terragrunt.hcl
file lives:
terraform {
source = "git::git@github.com:foo/modules.git//frontend-app?ref=v0.0.3"
extra_arguments "custom_vars" {
commands = [
"apply",
"plan",
"import",
"push",
"refresh"
]
# With the get_terragrunt_dir() function, you can use relative paths!
arguments = [
"-var-file=${get_terragrunt_dir()}/../common.tfvars"
]
}
}
For the example above, this path will resolve to /terraform-code/frontend-app/../common.tfvars
, which is exactly what you want.
get_parent_terragrunt_dir()
returns the absolute directory where the Terragrunt parent configuration file (by default terragrunt.hcl
) lives. This is useful when you need to use relative paths with remote Terraform configurations and you want those paths relative to your parent Terragrunt configuration file and not relative to the temporary directory where Terragrunt downloads the code.
This function is very similar to get_terragrunt_dir() except it returns the root instead of the leaf of your terragrunt configuration folder.
/terraform-code
├── terragrunt.hcl
├── common.tfvars
├── app1
│ └── terragrunt.hcl
├── tests
│ ├── app2
│ | └── terragrunt.hcl
│ └── app3
│ └── terragrunt.hcl
terraform {
extra_arguments "common_vars" {
commands = [
"apply",
"plan",
"import",
"push",
"refresh"
]
arguments = [
"-var-file=${get_parent_terragrunt_dir()}/common.tfvars"
]
}
}
The common.tfvars located in the terraform root folder will be included by all applications, whatever their relative location to the root.
get_terraform_commands_that_need_vars()
returns the list of terraform commands that accept -var
and -var-file
parameters. This function is used when defining extra_arguments.
terraform {
extra_arguments "common_var" {
commands = get_terraform_commands_that_need_vars()
arguments = ["-var-file=${get_aws_account_id()}.tfvars"]
}
}
get_terraform_commands_that_need_input()
returns the list of terraform commands that accept the -input=(true or false)
parameter. This function is used when defining extra_arguments.
terraform {
# Force Terraform to not ask for input value if some variables are undefined.
extra_arguments "disable_input" {
commands = get_terraform_commands_that_need_input()
arguments = ["-input=false"]
}
}
get_terraform_commands_that_need_locking()
returns the list of terraform commands that accept the -lock-timeout
parameter. This function is used when defining extra_arguments.
terraform {
# Force Terraform to keep trying to acquire a lock for up to 20 minutes if someone else already has the lock
extra_arguments "retry_lock" {
commands = get_terraform_commands_that_need_locking()
arguments = ["-lock-timeout=20m"]
}
}
get_terraform_commands_that_need_parallelism()
returns the list of terraform commands that accept the -parallelism
parameter. This function is used when defining extra_arguments.
terraform {
# Force Terraform to run with reduced parallelism
extra_arguments "parallelism" {
commands = get_terraform_commands_that_need_parallelism()
arguments = ["-parallelism=5"]
}
}
get_aws_account_id()
returns the AWS account id associated with the current set of credentials. Example:
remote_state {
backend = "s3"
config = {
bucket = "mycompany-${get_aws_account_id()}"
}
}
get_aws_caller_identity_arn()
returns the ARN of the AWS identity associated with the current set of credentials. Example:
inputs = {
caller_arn = get_aws_caller_identity_arn()
}
get_terraform_command()
returns the current terraform command in execution. Example:
inputs = {
current_command = get_terraform_command()
}
get_terraform_cli_args()
returns cli args for the current terraform command in execution. Example:
inputs = {
current_cli_args = get_terraform_cli_args()
}
get_aws_caller_identity_user_id()
returns the UserId of the AWS identity associated with the current set of credentials. Example:
inputs = {
caller_user_id = get_aws_caller_identity_user_id()
}
This allows uniqueness of the storage bucket per AWS account (since bucket name must be globally unique).
It is also possible to configure variables specifically based on the account used:
terraform {
extra_arguments "common_var" {
commands = get_terraform_commands_that_need_vars()
arguments = ["-var-file=${get_aws_account_id()}.tfvars"]
}
}
run_cmd(command, arg1, arg2…)
runs a shell command and returns the stdout as the result of the interpolation. The command is executed at the same folder as the terragrunt.hcl
file. This is useful whenever you want to dynamically fill in arbitrary information in your Terragrunt configuration.
As an example, you could write a script that determines the bucket and DynamoDB table name based on the AWS account, instead of hardcoding the name of every account:
remote_state {
backend = "s3"
config = {
bucket = run_cmd("./get_names.sh", "bucket")
dynamodb_table = run_cmd("./get_names.sh", "dynamodb")
}
}
If the command you are running has the potential to output sensitive values, you may wish to redact the output from appearing in the terminal. To do so, use the special --terragrunt-quiet
argument which must be passed as the first argument to run_cmd()
:
super_secret_value = run_cmd("--terragrunt-quiet", "./decrypt_secret.sh", "foo")
Note: This will prevent terragrunt from displaying the output from the command in its output. However, the value could still be displayed in the Terraform output if Terraform does not treat it as a sensitive value.
read_terragrunt_config(config_path, [default_val])
parses the terragrunt config at the given path and serializes the
result into a map that can be used to reference the values of the parsed config. This function will expose all blocks
and attributes of a terragrunt config.
For example, suppose you had a config file called common.hcl
that contains common input variables:
inputs = {
stack_name = "staging"
account_id = "1234567890"
}
You can read these inputs in another config by using read_terragrunt_config
, and merge them into the inputs:
locals {
common_vars = read_terragrunt_config(find_in_parent_folders("common.hcl"))
}
inputs = merge(
local.common_vars.inputs,
{
# additional inputs
}
)
This function also takes in an optional second parameter which will be returned if the file does not exist:
locals {
common_vars = read_terragrunt_config(find_in_parent_folders("i-dont-exist.hcl", "i-dont-exist.hcl"), {inputs = {}})
}
inputs = merge(
local.common_vars.inputs, # This will be {}
{
# additional inputs
}
)
Note that this function will also render dependency
blocks. That is, the parsed config will make the outputs of the
dependency
blocks available. For example, suppose you had the following config in a file called common_deps.hcl
:
dependency "vpc" {
config_path = "${get_terragrunt_dir()}/../vpc"
}
You can access the outputs of the vpc dependency through the parsed outputs of read_terragrunt_config
:
locals {
common_deps = read_terragrunt_config(find_in_parent_folders("common_deps.hcl"))
}
inputs = {
vpc_id = local.common_deps.dependency.vpc.outputs.vpc_id
}
sops_decrypt_file(file_path)
decrypts a yaml or json file encrypted with sops
.
sops is an editor of encrypted files that supports YAML, JSON, ENV, INI and BINARY formats and encrypts with AWS KMS, GCP KMS, Azure Key Vault and PGP.
This allows static secrets to be stored encrypted within your Terragrunt repository.
Only YAML and JSON formats are supported by sops_decrypt_file
For example, suppose you have some static secrets required to bootstrap your
infrastructure in secrets.yaml
, you can decrypt and merge them into the inputs
by using sops_decrypt_file
:
locals {
secret_vars = yamldecode(sops_decrypt_file(find_in_parent_folders("secrets.yaml")))
}
inputs = merge(
local.secret_vars,
{
# additional inputs
}
)
If you absolutely need to fallback to a default value you can make use of the Terraform try
function:
locals {
secret_vars = try(jsondecode(sops_decrypt_file(find_in_parent_folders("no-secrets-here.json"))), {})
}
inputs = merge(
local.secret_vars, # This will be {}
{
# additional inputs
}
)