Hooks
Before Hooks, After Hooks and Error Hooks are a feature of terragrunt that make it possible to define custom actions that will be called before/after running an tofu/terraform command.
They allow you to orchestrate certain operations around IaC updates so that you have a consistent way to run custom code before or after running OpenTofu/Terraform.
Here’s an example:
terraform { before_hook "before_hook" { commands = ["apply", "plan"] execute = ["echo", "Running OpenTofu"] }
after_hook "after_hook" { commands = ["apply", "plan"] execute = ["echo", "Finished running OpenTofu"] run_on_error = true }
error_hook "import_resource" { commands = ["apply"] execute = ["echo", "Error Hook executed"] on_errors = [ ".*", ] }}In this example configuration, whenever Terragrunt runs tofu apply or tofu plan (or the terraform equivalent), three things will happen:
- Before Terragrunt runs
tofu/terraform, it will outputRunning OpenTofuto the console. - After Terragrunt runs
tofu/terraform, it will outputFinished running OpenTofu, regardless of whether the command failed. - If an error occurs during the
tofu applycommand, Terragrunt will outputError Hook executed.
You can learn more about all the various configuration options supported in the reference docs for the terraform block.
Hook Context
Section titled “Hook Context”All hooks add extra environment variables when executing the hook’s run command:
TG_CTX_TF_PATHTG_CTX_COMMANDTG_CTX_HOOK_NAME
For example:
terraform { before_hook "test_hook" { commands = ["apply"] execute = ["hook.sh"] }}Where hook.sh is:
echo "TF_PATH=${TG_CTX_TF_PATH} COMMAND=${TG_CTX_COMMAND} HOOK_NAME=${TG_CTX_HOOK_NAME}"Will result in the following output when Terragrunt runs tofu apply/terraform apply:
TF_PATH=tofu COMMAND=apply HOOK_NAME=test_hookNote that hooks are executed within the working directory where OpenTofu/Terraform would be run.
If using the source attribute for the terraform block, this will result in the hook running in
the hidden .terragrunt-cache directory.
This also means that you can use tofu/terraform commands within hooks to access any outputs needed
for hook logic.
For example:
# Get the bucket_name output from OpenTofu/Terraform stateBUCKET_NAME="$("$TG_CTX_TF_PATH" output -raw bucket_name)"
# Use the AWS CLI to list the contents of the bucketaws s3 ls "s3://$BUCKET_NAME"Note that the TG_CTX_TF_PATH environment variable is used here to ensure compatibility, regardless of the
value of tf-path. This can be a useful practice
if you are migrating between OpenTofu or Terraform.
You will also have access to all the inputs set in the terragrunt.hcl file as environment variables prefixed
by TF_VAR_, as that’s how the variables are set for use in OpenTofu/Terraform.
For example, if you have the following inputs block in your terragrunt.hcl file:
inputs = { bucket_name = "my-bucket"}You can access the bucket_name input in your hook as follows:
# Get the bucket_name input from the terragrunt.hcl fileBUCKET_NAME="$TF_VAR_bucket_name"
# Use the AWS CLI to list the contents of the bucketaws s3 ls "s3://$BUCKET_NAME"Orchestrating execution outside IaC
Section titled “Orchestrating execution outside IaC”Hooks can be used to handle operations that need to happen, but are not directly related to the OpenTofu/Terraform.
For example, you may be using Terragrunt to manage an AWS ECS service.
You can use a before_hook to build and push a new image to the Elastic Container Registry (ECR) before running tofu apply.
terraform { before_hook "build_and_push_image" { commands = ["plan", "apply"] execute = ["./build_and_push_image.sh"] }}Where build_and_push_image.sh is something like:
#!/usr/bin/env bash
set -eou pipefail
ACCOUNT_ID="123456789012"REGION="us-east-1"REPOSITORY="my-repository"TAG="latest"
IMAGE_TAG="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${REPOSITORY}:${TAG}"
# Build the Docker imagedocker build -t "$IMAGE_TAG" .
# Push the Docker image to ECRaws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.comdocker push "$IMAGE_TAG"The hard-coding of values in the script could be replaced with context, as shown in the previous section.
Similarly, you may want to smoke-test newly deployed infrastructure after running tofu apply.
terraform { after_hook "smoke_test" { commands = ["apply"] execute = ["./smoke_test.sh"] run_on_error = true }}Where smoke_test.sh is something like:
#!/usr/bin/env bash
set -eou pipefail
# Get the URL for the service from OpenTofu/Terraform stateSERVICE_URL="$("$TG_CTX_TF_PATH" output -raw service_url)"
# Use curl to check the service is upcurl -sSf "$SERVICE_URL"You might even decide to integrate with a product like Terratest for more complex testing.
Hook Ordering
Section titled “Hook Ordering”You can have multiple before and after hooks. Each hook will execute in the order they are defined.
For example:
terraform { before_hook "before_hook_1" { commands = ["apply", "plan"] execute = ["echo", "Will run OpenTofu"] }
before_hook "before_hook_2" { commands = ["apply", "plan"] execute = ["echo", "Running OpenTofu"] }}This configuration will cause Terragrunt to output Will run OpenTofu and then Running OpenTofu before the call
to OpenTofu/Terraform.
Tflint hook
Section titled “Tflint hook”Before Hooks or After Hooks natively support tflint, a linter for OpenTofu/Terraform code. It will validate the OpenTofu/Terraform code used by Terragrunt, and its inputs.
Here’s an example:
terraform { before_hook "before_hook" { commands = ["apply", "plan"] execute = ["tflint"] }}The .tflint.hcl should exist in the same folder as terragrunt.hcl or one of it’s parents. If Terragrunt can’t find
a .tflint.hcl file, it won’t execute tflint and return an error. All configurations should be in a config block in this
file, as per Tflint’s docs.
plugin "aws" { enabled = true version = "0.21.0" source = "github.com/terraform-linters/tflint-ruleset-aws"}
config { module = true}Configuration
Section titled “Configuration”By default, tflint is executed with the internal tflint built into Terragrunt, which will evaluate parameters passed in.
Any desired extra configuration should be added in the .tflint.hcl file.
It will work with a .tflint.hcl file in the current folder or any parent folder.
To utilize an alternative configuration file, use the --config flag with the path to the configuration file.
If there is a need to run tflint from the operating system directly, use the extra parameter --external-tflint.
This will result in usage of the tflint binary found in the PATH environment variable.
For example:
terraform { before_hook "tflint" { commands = ["apply", "plan"] execute = ["tflint" , "--external-tflint", "--minimum-failure-severity=error", "--config", "custom.tflint.hcl"] }}Authentication for tflint rulesets
Section titled “Authentication for tflint rulesets”Public rulesets
tflint works without any authentication for public rulesets (hosted on public repositories).
Private rulesets
If you want to run the tflint hook with custom rulesets defined in a private repository, you will need to export a valid GITHUB_TOKEN token.
Troubleshooting
Section titled “Troubleshooting”flag provided but not defined: -act-as-bundled-plugin error
If you have an .tflint.hcl file that is empty, or uses the terraform ruleset without version or source constraint, it can return the following error:
Failed to initialize plugins; Unrecognized remote plugin message: Incorrect Usage. flag provided but not defined: -act-as-bundled-pluginTo fix this, make sure that the configuration for the terraform ruleset, in the .tflint.hcl file contains a version constraint:
plugin "terraform" { enabled = true version = "0.2.1" source = "github.com/terraform-linters/tflint-ruleset-terraform"}