Shape
Shape

Built-in functions

Functions

Terragrunt allows you to use built-in functions anywhere in terragrunt.hcl, just like Terraform! The functions currently available are:

Terraform built-in functions

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

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.tfvars", "fallback.tfvars")
}

path_relative_to_include

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

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

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

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

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

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

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

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

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

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

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

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

get_terraform_command() returns the current terraform command in execution. Example:

inputs = {
  current_command = get_terraform_command()
}

get_terraform_cli_args

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

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

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

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

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
  }
)