---
title: Functions
description: Learn about the built-in functions available in Terragrunt.
slug: reference/hcl/functions
sidebar:
  order: 4
---

import { Aside } from '@astrojs/starlight/components';
import FileTree from '@components/vendored/starlight/FileTree.astro';
import Before from '@components/Before.astro';

Terragrunt allows you to use built-in functions anywhere in `terragrunt.hcl`, just like OpenTofu/Terraform\!

## OpenTofu/Terraform built-in functions

All [OpenTofu/Terraform built-in functions (as of v0.15.3)](https://opentofu.org/docs/language/functions/) are supported in Terragrunt config files:

```hcl
# terragrunt.hcl

terraform {
  source = "../modules/${basename(get_terragrunt_dir())}"
}

remote_state {
  backend = "s3"
  config = {
    bucket = trimspace("   my-tofu-bucket     ")
    region = join("-", ["us", "east", "1"])
    key    = format("%s/tofu.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:

<FileTree>

- terragrunt
  - common.tfvars
  - assets
    - mysql
      - assets.txt
  - terragrunt.hcl

</FileTree>

Then `assets.txt` could be read with the following function call:

```hcl
file("assets/mysql/assets.txt")
```

**Note:**

Terragrunt was originally able to take advantage of built-in OpenTofu/Terraform built-in functions automatically, as they were exposed via an exported package. Since `v0.15.3`, however, these functions are now `internal` to the respective codebases.

As a result, Terragrunt users typically use different functions to resolve the same problems. e.g. Terragrunt users can execute arbitrary shell commands with [run\_cmd](#run_cmd) in whatever language they like instead of using a bespoke HCL function to solve a given problem. In the future, OpenTofu may expose these functions via a public package, which would allow Terragrunt to access them directly. Until such a time, Terragrunt will continue to provide its own set of functions to solve problems relevant to Terragrunt users.

If there is a specific function you would like to see supported directly in Terragrunt, please [open an issue](https://github.com/gruntwork-io/terragrunt/issues) requesting it. Make sure to include the use case you have in mind so we can understand the problem you are trying to solve, and why existing Terragrunt functions are not sufficient.

## 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 file in a parent folder with a given name, or exits 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 configuration:

```hcl
# some/folder/terragrunt.hcl

include "root" {
  path = find_in_parent_folders("root.hcl")
}
```

The function can also be used to find parent folders.

```hcl
# some/folder/terragrunt.hcl

include "root" {
  path = find_in_parent_folders("some")
}
```

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:

```hcl
# some/folder/terragrunt.hcl

include "root" {
  path = find_in_parent_folders("some-other-file-name.hcl", "fallback.hcl")
}
```

Note that this function searches relative to the `terragrunt.hcl` file when called from a parent config. For
example, if you had the following filesystem layout:

<FileTree>

- root.hcl
- prod
  - env.hcl
  - mysql
    - terragrunt.hcl

</FileTree>

And the root `root.hcl` contained the following:

```hcl
# root.hcl

locals {
  env_vars = read_terragrunt_config(find_in_parent_folders("env.hcl"))
}
```

The `find_in_parent_folders` will search from the **child `terragrunt.hcl`** (`prod/mysql/terragrunt.hcl`) config,
finding the `env.hcl` file in the `prod` directory.

**NOTE:** This function has undocumented behavior that has since been deprecated. To learn more about this, see the [Migrating from root `terragrunt.hcl`](/migrate/migrating-from-root-terragrunt-hcl) guide.

## 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 filesystem layout:

<FileTree>

- root.hcl
- prod
  - mysql
    - terragrunt.hcl
- stage
  - mysql
    - terragrunt.hcl

</FileTree>

Imagine `prod/mysql/terragrunt.hcl` and `stage/mysql/terragrunt.hcl` include all settings from the root `root.hcl` file:

```hcl
# prod/mysql/terragrunt.hcl

include "root" {
  path = find_in_parent_folders("root.hcl")
}
```

The root `root.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`:

```hcl
# root.hcl

remote_state {
  backend = "s3"
  config = {
    bucket = "my-tofu-bucket"
    region = "us-east-1"
    key    = "${path_relative_to_include()}/tofu.tfstate"
  }
}
```

The resulting `key` will be `prod/mysql/tofu.tfstate` for the prod `mysql` module and `stage/mysql/tofu.tfstate` for the stage `mysql` module.

If you have `include` blocks, this function requires a `name` parameter when used in the child config to specify which
`include` block to base the relative path on.

Example:

```hcl
# prod/mysql/terragrunt.hcl

include "root" {
  path = find_in_parent_folders("root.hcl")
}

include "region" {
  path = find_in_parent_folders("region.hcl")
}

terraform {
  source = "../modules/${path_relative_to_include("root")}"
}
```

## 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 filesystem layout:

<FileTree>

- sources
  - mysql
    - \*.tf
  - secrets
    - mysql
      - \*.tf
- terragrunt
  - root.hcl
  - common.tfvars
  - mysql
    - terragrunt.hcl
  - secrets
    - mysql
      - terragrunt.hcl

</FileTree>

Imagine `terragrunt/mysql/terragrunt.hcl` and `terragrunt/secrets/mysql/terragrunt.hcl` include all settings from the root `root.hcl` file:

```hcl
# terragrunt/mysql/terragrunt.hcl

include "root" {
  path = find_in_parent_folders("root.hcl")
}
```

The root `root.hcl` can use the `path_relative_from_include()` in combination with `path_relative_to_include()` in its `source` configuration to retrieve the relative OpenTofu/Terraform source code from the terragrunt configuration file:

```hcl
# root.hcl

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 an extra argument to include the `common.tfvars` file for all subdirectories:

```hcl
# root.hcl

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.

If you have `include` blocks, this function requires a `name` parameter when used in the child config to specify which
`include` block to base the relative path on.

Example:

```hcl
# prod/mysql/terragrunt.hcl

include "root" {
  path = find_in_parent_folders("root.hcl")
}

include "region" {
  path = find_in_parent_folders("region.hcl")
}

terraform {
  source = "../modules/${path_relative_from_include("root")}"
}
```

## get_env

`get_env(NAME)` return the value of variable named `NAME` or throws exceptions if that variable is not set. Example:

```hcl
# terragrunt.hcl

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:

```hcl
# terragrunt.hcl

remote_state {
  backend = "s3"
  config = {
    bucket = get_env("BUCKET", "my-tofu-bucket")
  }
}
```

Note that [OpenTofu/Terraform will read environment variables](https://opentofu.org/docs/cli/config/environment-variables/#tf_var_name) that start with the prefix `TF_VAR_`, so one way to share a variable named `foo` between OpenTofu/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:

```hcl
# terragrunt.hcl

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:

```hcl
# outputs.tf

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_repo_root

`get_repo_root()` returns the absolute path to the root of the Git repository:

```hcl
# terragrunt.hcl

inputs {
  very_important_config = "${get_repo_root()}/config/strawberries.conf"
}
```

This function will error if the file is not located in a Git repository.

## get_path_from_repo_root

`get_path_from_repo_root()` returns the path from the root of the Git repository to the current directory:

```hcl
# terragrunt.hcl

remote_state {
  backend = "s3"

  config = {
    bucket         = "tofu"
    dynamodb_table = "tofu"
    encrypt        = true
    key            = "${get_path_from_repo_root()}/tofu.tfstate"
    session_name   = "tofu"
    region         = "us-east-1"
  }
}
```

This function will error if the file is not located in a Git repository.

## get_path_to_repo_root

`get_path_to_repo_root()` returns the relative path to the root of the Git repository:

```hcl
# terragrunt.hcl

terraform {
  source = "${get_path_to_repo_root()}//modules/example"
}
```

This function will error if the file is not located in a Git repository.

## 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 OpenTofu/Terraform configurations](/features/units/#remote-opentofuterraform-modules) 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:

<FileTree>

- common.tfvars
- frontend-app
  - terragrunt.hcl

</FileTree>

Inside `tofu-code/frontend-app/terragrunt.hcl` you might try to write code that looks like this:

```hcl
# tofu-code/frontend-app/terragrunt.hcl

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 `tofu`/`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:

```hcl
# tofu-code/frontend-app/terragrunt.hcl

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"
    ]
  }
}
```

## get_working_dir

`get_working_dir()` returns the absolute path where Terragrunt runs OpenTofu/Terraform commands. This is useful when you need to manage substitutions of vars inside a \*.tfvars file located right inside terragrunt's tmp dir.

## get_parent_terragrunt_dir

`get_parent_terragrunt_dir()` returns the absolute directory where the Terragrunt parent configuration file lives (regardless of what it's called). This is useful when you need to use relative paths with [remote OpenTofu/Terraform configurations](/features/units/#remote-opentofuterraform-modules) 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()](#get_terragrunt_dir) except it returns the root instead of the leaf of your terragrunt configurations.

<FileTree>

- root.hcl
- common.tfvars
- app1
  - terragrunt.hcl
- tests
  - app2
    - terragrunt.hcl
  - app3
    - terragrunt.hcl

</FileTree>

```hcl
# root.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 root folder will be included by all applications, whatever their relative location to the root.

If you have `include` blocks, this function requires a `name` parameter when used in the child config to specify which
`include` block to base the parent dir on.

Example:

```hcl
# prod/mysql/terragrunt.hcl

include "root" {
  path = find_in_parent_folders("root.hcl")
}

include "region" {
  path = find_in_parent_folders("region.hcl")
}

terraform {
  source = "${get_parent_terragrunt_dir("root")}/modules/vpc"
}
```

## get_original_terragrunt_dir

`get_original_terragrunt_dir()` returns the directory where the original Terragrunt configuration file (by default
`terragrunt.hcl`) lives. This is primarily useful when one Terragrunt config is being read from another: e.g., if
`/tofu-code/terragrunt.hcl` calls `read_terragrunt_config("/foo/bar.hcl")`, and within `bar.hcl`, you call
`get_original_terragrunt_dir()`, you'll get back `/tofu-code`.

During stack generation, this function returns the
directory of the `terragrunt.stack.hcl` currently being read, even when invoked from other Terragrunt
configs via `read_terragrunt_config()`.

## get_terraform_commands_that_need_vars

`get_terraform_commands_that_need_vars()` returns the list of OpenTofu/Terraform commands that accept `-var` and `-var-file` parameters. This function is used when defining [extra_arguments](/features/units/extra-arguments/#multiple-extra_arguments-blocks).

```hcl
# terragrunt.hcl

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 OpenTofu/Terraform commands that accept the `-input=(true or false)` parameter. This function is used when defining [extra_arguments](/features/units/extra-arguments/#multiple-extra_arguments-blocks).

```hcl
# terragrunt.hcl

terraform {
  # Force OpenTofu/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](/features/units/extra-arguments/#multiple-extra_arguments-blocks).

```hcl
# terragrunt.hcl

terraform {
  # Force OpenTofu/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](/features/units/extra-arguments/#multiple-extra_arguments-blocks).

```hcl
# terragrunt.hcl

terraform {
  # Force OpenTofu/Terraform to run with reduced parallelism
  extra_arguments "parallelism" {
    commands  = get_terraform_commands_that_need_parallelism()
    arguments = ["-parallelism=5"]
  }
}
```

## get_aws_account_alias

`get_aws_account_alias()` returns the AWS account alias associated with the current set of credentials. If the alias cannot be found, it will return an empty string. Example:

```hcl
# terragrunt.hcl

inputs = {
  account_alias = get_aws_account_alias()
}
```

**Note:** value returned by `get_aws_account_alias()` can change during parsing of HCL code, for example after evaluation of `iam_role` attribute.

## get_aws_account_id

`get_aws_account_id()` returns the AWS account id associated with the current set of credentials. Example:

```hcl
# terragrunt.hcl

remote_state {
  backend = "s3"
  config = {
    bucket = "mycompany-${get_aws_account_id()}"
  }
}
```

**Note:** value returned by `get_aws_account_id()` can change during parsing of HCL code, for example after evaluation of `iam_role` attribute.

## 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:

```hcl
# terragrunt.hcl

inputs = {
  caller_arn = get_aws_caller_identity_arn()
}
```

**Note:** value returned by `get_aws_caller_identity_arn()` can change during parsing of HCL code, for example after evaluation of `iam_role` attribute.

## get_terraform_command

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

```hcl
# terragrunt.hcl

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:

```hcl
# terragrunt.hcl

inputs = {
  current_cli_args = get_terraform_cli_args()
}
```

## get_default_retryable_errors

`get_default_retryable_errors()` returns the default list of retryable error patterns Terragrunt uses for transient failures. Use it within the `errors` block to seed a `retry` configuration.

```hcl
# terragrunt.hcl

errors {
  retry "default_errors" {
    retryable_errors   = get_default_retryable_errors()
    max_attempts       = 3
    sleep_interval_sec = 5
  }

  retry "custom_errors" {
    retryable_errors   = [".*my custom error.*"]
    max_attempts       = 5
    sleep_interval_sec = 10
  }
}
```

## 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:

```hcl
# terragrunt.hcl

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:

```hcl
# terragrunt.hcl

terraform {
  extra_arguments "common_var" {
    commands = get_terraform_commands_that_need_vars()
    arguments = ["-var-file=${get_aws_account_id()}.tfvars"]
  }
}
```

**Note:** value returned by `get_aws_caller_identity_user_id()` can change during parsing of HCL code, for example after evaluation of `iam_role` attribute.

## 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.

### Basic Usage

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:

```hcl
# terragrunt.hcl

remote_state {
  backend = "s3"
  config = {
    bucket         = run_cmd("./get_names.sh", "bucket")
    dynamodb_table = run_cmd("./get_names.sh", "dynamodb")
  }
}
```

### Special Parameters

The `run_cmd` function accepts some special flags that can alter how the function evaluates commands on your behalf. Placing these `--terragrunt-` prefixed flags as the first argument(s) of a `run_cmd` call will result in the behavior of `run_cmd` being adjusted. You can mix and match the flags in any order, so long as they precede the command you are running with `run_cmd`.

| Parameter                   | Description                                                                                                                                                                                   | Example                                                                                                               |
|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| `--terragrunt-quiet`        | Redacts `run_cmd` stdout from Terragrunt logs while still returning the value to HCL. This keeps sensitive information out of log files.                                                      | `run_cmd("--terragrunt-quiet", "./decrypt_secret.sh", "foo")`                                                         |
| `--terragrunt-global-cache` | Stores and reuses results in a global cache so the command only runs once per set of arguments, no matter which configuration references it. Useful when the output is directory-independent. | `run_cmd("--terragrunt-global-cache", "aws", "sts", "get-caller-identity", "--query", "Account", "--output", "text")` |
| `--terragrunt-no-cache`     | Skips the cache entirely and forces the command to run on every evaluation. Use this when the output changes frequently (timestamps, tokens, random IDs, etc.).                               | `run_cmd("--terragrunt-no-cache", "date", "+%s")`                                                                     |

Terragrunt caches `run_cmd` results by default to avoid running the same command multiple times during parsing. The cache key includes the directory of the `terragrunt.hcl` file and the command arguments unless you opt into global caching or disable caching entirely.
Parameters `--terragrunt-global-cache` and `--terragrunt-no-cache` are mutually exclusive, Terragrunt will return an error if both are provided.

#### Examples

**Suppress output for sensitive values:**

```hcl
# Output is redacted in logs, but still available to Terragrunt
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 OpenTofu/Terraform output if OpenTofu/Terraform does not treat it as a [sensitive value](https://www.terraform.io/docs/configuration/outputs.html#sensitive-suppressing-values-in-cli-output).

**Use global cache for directory-independent commands:**

```hcl
# Same result regardless of which directory run_cmd is called from
account_id = run_cmd("--terragrunt-global-cache", "aws", "sts", "get-caller-identity", "--query", "Account", "--output", "text")
```

**Disable caching for dynamic values:**

```hcl
# Generates a new UUID every time run_cmd is evaluated
build_id = run_cmd("--terragrunt-no-cache", "uuidgen")

# Gets current timestamp on each parse of the Terragrunt configuration
timestamp = run_cmd("--terragrunt-no-cache", "date", "+%s")
```

**Combine multiple parameters:**

```hcl
# Disable cache AND suppress output for a sensitive dynamic value
session_token = run_cmd("--terragrunt-no-cache", "--terragrunt-quiet", "./generate-temp-token.sh")

### Caching Behavior

By default, invocations of `run_cmd` are cached based on the current directory and executed command, so cached values are reused later rather than executed multiple times. Here's an example:

```hcl
# terragrunt.hcl

locals {
  uuid = run_cmd("echo", "uuid1",  uuid())
  uuid2 = run_cmd("echo", "uuid2", uuid())
  uuid3 = run_cmd("echo", "uuid3", uuid())
  potato = run_cmd("echo", "potato")
  potato2 = run_cmd("echo", "potato")
  carrot = run_cmd("echo", "carrot")
}
inputs = {
  potato3 = run_cmd("echo", "potato")
  uuid3 = run_cmd("echo", "uuid3", uuid())
  uuid4 = run_cmd("echo", "uuid4", uuid())
  carrot2 = run_cmd("echo", "carrot")
}
```

Output:

```bash
$ terragrunt init
uuid1 b48379e1-924d-2403-8789-c72d50be964c
uuid1 9f3a8398-b11f-5314-7783-dad176ee487d
uuid1 649ac501-e5db-c935-1499-c59fb7a75625
uuid2 2d65972b-3fa9-181f-64fe-dcd574d944d0
uuid3 e345de60-9cfa-0455-79b7-af0d053a15a5
potato
uuid3 7f90a4ed-96e3-1dd8-5fee-91b8c8e07650
uuid2 8638fe79-c589-bebd-2a2a-3e6b96f7fc34
uuid3 310d0447-f0a6-3f67-efda-e6b1521fa1fb
uuid4 f8e80cc6-1892-8db7-bd63-6089fef00c01
uuid2 289ff371-8021-54c6-2254-72de9d11392a
uuid3 baa19863-1d99-e0ef-11f2-ede830d1c58a
carrot
```

**Key observations from the output:**

- `carrot` and `potato` appear once because subsequent invocations used cached values
- `uuid1`, `uuid2`, and `uuid3` appear multiple times because each call to `uuid()` generates a different cache key
- `uuid3` appears one extra time because it's declared in both `locals` and `inputs`
- `uuid4` appears once since it's declared in `inputs`, which is evaluated once

This caching behavior can be modified using the special parameters described in the [Special Parameters](#special-parameters) section above.

## deep_merge

`deep_merge(map1, map2, ...)` deeply merges maps/objects. For overlapping keys, values from later arguments override earlier ones. Nested maps are merged recursively, and lists are appended. This function takes positional arguments.

`deep_merge` requires the [`deep-merge`](/reference/experiments/active#deep-merge) experiment. Calling it without the experiment enabled returns an error.

Any `null` arguments are ignored.

Basic example:

```hcl
locals {
  config = deep_merge(
    { service = { retries = 1, mode = "safe" } },
    { service = { retries = 3 }, tags = { env = "dev" } },
  )
}
```

Example with multiple JSON files (`...` expands a list into positional arguments):

```hcl
locals {
  config_json_files = sort(fileset(get_terragrunt_dir(), "*.json"))
  config = deep_merge([
    for file in local.config_json_files :
    jsondecode(file("${get_terragrunt_dir()}/${file}"))
  ]...)
}

inputs = local.config
```

## 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:

```hcl
# common.hcl

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:

```hcl
# terragrunt.hcl

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:

```hcl
# terragrunt.hcl

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`:

```hcl
# 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`:

```hcl
# terragrunt.hcl

locals {
  common_deps = read_terragrunt_config(find_in_parent_folders("common_deps.hcl"))
}

inputs = {
  vpc_id = local.common_deps.dependency.vpc.outputs.vpc_id
}
```

Notes:

- `read_terragrunt_config` can be also used to read `terragrunt.stack.hcl` and `terragrunt.values.hcl` files.

## sops_decrypt_file

`sops_decrypt_file(file_path)` decrypts a yaml, json, ini, env or "raw text" file encrypted with `sops`.

[sops](https://github.com/getsops/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, Hashicorp Vault and PGP.

This allows static secrets to be stored encrypted within your Terragrunt repository.

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`:

```hcl
# terragrunt.hcl

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 OpenTofu/Terraform `try` function:

```hcl
# terragrunt.hcl

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

## get_terragrunt_source_cli_flag

`get_terragrunt_source_cli_flag()` returns the value passed in via the CLI `--source` or an environment variable `TG_SOURCE`. Note that this will return an empty string when either of those values are not provided.

This is useful for constructing before and after hooks, or TF flags that only apply to local development (e.g., setting up debug flags, or adjusting the `iam_role` parameter).

Some example use cases are:

- Setting debug logging when doing local development.
- Adjusting the kubernetes provider configuration so that it targets minikube instead of real clusters.
- Providing special mocks pulled in from the local dev source (e.g., something like `mock_outputs = jsondecode(file("${get_terragrunt_source_cli_arg()}/dependency_mocks/vpc.json"))`).

## read_tfvars_file

`read_tfvars_file(file_path)` reads a `.tfvars` or `.tfvars.json` file and returns a map of the variables defined in it.

This is useful for reading variables from a `.tfvars` file, merging them into the inputs, or using them in a `locals` block:

```hcl
# terragrunt.hcl

locals {
  inputs_from_tfvars = jsondecode(read_tfvars_file("common.tfvars"))
}

inputs = merge(
  local.inputs_from_tfvars,
  {
    # additional inputs
  }
)
```

Another example:

```hcl
# terragrunt.hcl

locals {
  backend = jsondecode(read_tfvars_file("backend.tfvars"))
}

remote_state {
  backend = "s3"
  config = {
    bucket         = "${get_env("TG_BUCKET_PREFIX", "tf-bucket")}-${get_aws_account_id()}"
    key            = "${path_relative_to_include()}/terraform-${local.aws_region}.tfstate"
    region         = local.backend.region
  }
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite_terragrunt"
  }
}
```

## mark_as_read

`mark_as_read(file_path)` marks a file as read so that it can be picked up for inclusion by the [queue-include-units-reading](/reference/cli/commands/run#queue-include-units-reading) flag.

This is useful for situations when you want to mark a file as read, but are not reading it using a native Terragrunt HCL function.

For example:

```hcl
# terragrunt.hcl

locals {
  filename   = mark_as_read("/path/to/my/file-read-by-tofu.txt")
  many_files = [for f in fileset("./config", "*.yaml") : file(mark_as_read(abspath("${get_terragrunt_dir()}/config/${f}")))]
}

inputs = {
  filename   = local.filename
  many_files = local.many_files
}
```

By using `mark_as_read` on `file-read-by-tofu.txt`, you can ensure that the `terragrunt.hcl` file passing in the `file-read-by-tofu.txt` file as an input will be included in
any `run --all` run where the flag `--queue-include-units-reading file-read-by-tofu.txt` is set.

The same technique can be used to mark a file as read when a file is read using code in `run_cmd`.

**NOTE**: Due to the way that Terragrunt enqueues files we require an absolute path for mark_as_read to avoid multiple inclusions.

**NOTE**: Due to the way that Terragrunt parses configurations during a `run --all`, functions will only properly mark files as read
if they are used in the `locals` block. Reading a file directly in the `inputs` block will not mark the file as read, as the `inputs`
block is not evaluated until *after* the queue has been populated with units to run.

## mark_glob_as_read

<Before version="1.1.0">
<Aside type="caution">
`mark_glob_as_read` requires the [`mark-many-as-read`](/reference/experiments/active#mark-many-as-read) experiment. Calling it without the experiment enabled returns an error.
</Aside>
</Before>

<Aside type="caution">
`mark_glob_as_read` writes to the same read tracker as [`mark_as_read`](#mark_as_read), so the same evaluation-order caveat applies: during a `run --all`, the queue is populated before `inputs` is evaluated, so a call placed in `inputs` (or any other later-evaluated block) will not influence queue construction. Call `mark_glob_as_read` from `locals` to ensure matched files are considered when the queue is built.
</Aside>

`mark_glob_as_read(pattern)` expands the given glob and marks every matching file as read. It returns the list of absolute paths that matched, so it can be composed with other expressions.

Pattern syntax follows [`gobwas/glob`](https://github.com/gobwas/glob), the same matcher used elsewhere in Terragrunt. `/` is the path separator, `*` matches within a single segment, `**` matches any sequence of characters including separators, `?` matches any single non-separator character, `[abc]` matches a character class, and `{a,b}` matches any of the listed alternatives. A backslash escapes the following metacharacter.

Relative patterns are resolved against the current working directory, the same way `mark_as_read` resolves a relative file path.

<Aside type="caution">
Glob patterns must use forward slashes (`/`) as the path separator, even on Windows. The matcher treats `\` as an escape character, not a path separator, so a backslash-separated pattern will not match any files.
</Aside>

<Aside type="caution">
`**` only collapses the separators around it when the adjacent segments are literals. `config/**/*.yaml` matches files at least one directory below `config/`, but not files directly inside `config/`, because the trailing segment `*.yaml` is a wildcard. Use brace alternation to match at any depth:

```hcl
mark_glob_as_read("${get_terragrunt_dir()}/config/{*.yaml,**/*.yaml}")
```
</Aside>

```hcl
# terragrunt.hcl

locals {
  configs = mark_glob_as_read("${get_terragrunt_dir()}/config/{*.yaml,**/*.yaml}")
}

inputs = {
  configs = [for f in local.configs : yamldecode(file(f))]
}
```

A typical use case is a unit that consumes a directory of configuration files indirectly (for example, through `run_cmd`, `templatefile`, or a shared helper module) and wants changes to any of them to trigger the unit through [reading-based filter expressions](/features/filter/attributes#reading-based-expressions) such as `--filter 'reading=./config/**'`.

If the pattern matches nothing, the function returns an empty list without error.

### Boundary

`mark_glob_as_read` confines glob expansion to a boundary directory. By default the boundary is the enclosing Git repository root; outside a Git repository, no boundary applies. A pattern whose walk would begin outside the boundary returns an error rather than expanding.

This keeps a pattern that resolves higher than intended from walking a large portion of the filesystem. A pattern interpolated from an empty value is the common case: `"${local.dir}/{*.yaml}"` becomes `/{*.yaml}` when `local.dir` is `""`.

Pass a leading `--terragrunt-boundary` argument to set the boundary explicitly. Use it to scope the walk to a subdirectory, or to widen it when a scan outside the repository root is intended. A relative value resolves against the working directory.

```hcl
locals {
  # Restrict the walk to a subdirectory.
  scoped = mark_glob_as_read("--terragrunt-boundary=/etc/terragrunt", "/etc/terragrunt/{*.yaml}")

  # Remove the boundary entirely.
  everything = mark_glob_as_read("--terragrunt-boundary=/", "/{*.yaml}")
}
```

<Aside type="caution">
A `? :` conditional does not skip the call. HCL evaluates both branches of a conditional before selecting one, so a guard such as `local.dir != "" ? mark_glob_as_read(...) : []` still expands the glob. Wrap the call in [`try`](https://opentofu.org/docs/language/functions/try/) so a boundary error recovers to a fallback:

```hcl
locals {
  files = sort(try(mark_glob_as_read("${local.dir}/{*.yaml,*.yml,*.json}"), []))
}
```
</Aside>

## constraint_check

`constraint_check(version, constraint)` checks if a given version satisfies a given constraint.

This particularly is useful for situations where you want to change the runtime behavior of Terragrunt based on the version of an OpenTofu/Terraform module.

For example:

```hcl
feature "module_version" {
  default = "1.2.3"
}

locals {
  module_version       = feature.module_version.value
  needs_v2_adjustments = constraint_check(local.module_version, ">= 2.0.0")
}

terraform {
  source = "github.com/my-org/my-module.git//?ref=v${local.module_version}"
}

inputs = !local.needs_v2_adjustments ? {
  old_module_input_name = "old_module_input_value"
} : {
  new_module_input_name = "new_module_input_value"
}
```

In this example, the `v2.0.0` version of the module made a breaking change to rename an input variable from `old_module_input_name` to `new_module_input_name`.

Instead of carefully coordinating the version update with the corresponding input change, users can set a feature flag to control opt-in of the new module version, and have Terragrunt dynamically adjust the input variable name based on the constraint check, that the module version is greater than or equal to `2.0.0`.

The HCL function supports all the same constraints that you can use for version constraints in [terragrunt_version_constraint](/reference/hcl/attributes/#terragrunt_version_constraint) and [terraform_version_constraint](/reference/hcl/attributes/#terraform_version_constraint).
