A proposal to discuss with the Terragrunt community: adding "hooks" to Terragrunt that can be used to execute arbitrary shell commands. The idea would be to follow the basic structure of extra_arguments, where you can specify shell commands that will be executed either before or after specific Terraform commands. Here's the rough idea:
terragrunt = {
terraform {
# This hook configures Terragrunt to copy /foo to /bar before executing apply or plan
before_hook "copy-file" {
commands = ["apply", "plan"]
execute = ["cp", "/foo", "/bar"]
}
# This hook configures Terragrunt to do a simple echo statement after executing any Terraform command
after_hook "copy-file" {
commands = ["${all_terraform_commands()}"]
execute = ["echo", "Hello World"]
}
}
}
This idea has come up before, but I pushed back against it, recommending the use of Terraform's local-exec provisioner instead. However, I realize now that if you need to execute some code before or after terraform runs, local-exec doesn't help you.
A pattern that seems particularly interesting is to copy files into the working directory before running Terraform. This is especially useful for cases where Terraform doesn't support interpolation or effective code reuse. Examples:
Copy a version.tf file into your working directory. This file could define the required_version setting for your whole company so you don't have to copy/paste that setting into every single Terraform module.
Copy a common-vars.tf file into your working directory. This file could define common input variables that every single module should define: e.g., aws_region, aws_account_id, etc.
Copy a remote-state.tf file into your working directory and dynamically fill in the proper key and bucket values. Instead of relying on Terragrunt for keeping your remote state configuration DRY, you could use this approach to execute arbitrary code, and have a more flexible/customizable system.
Thoughts? Feedback? Concerns?
That is exactly what I am implementing using another shell wrapper script and I really don't like my approach. My use case is described in example 1 and 2.
My feedback is that hooks in child terraform.tfvars and in a parent's one should be possible to combine, because the one in a child sometimes has some specific steps, while the one in parent should be applied for all cases.
I'm interested in the hooks pattern too. But for the purpose of halting an operation if some condition isn't met.
For instance. If we could assign a hook that validates that the git repo is totally up to date with origin and there are no uncommitted/unpushed changes that would help us enforce good practices when working with our -live repo.
@john-mcgowan-wowza If a hook exits with a non-zero exit code, it would halt Terragrunt execution. This would work very simply & intuitively for a "pre" hook to check for uncommitted changes.
I like the idea of starting out with the generic hook functionality. And then if a particular hook pattern is fundamental enough, we could add it as part of terragrunt itself. So initially it might look like this...
before_hook "check-git-clean" {
commands = ["apply", "plan"]
execute = ["check-git-clean.sh"]
}
but then when everybody ends up relying on the same functionality provided by check-git-clean.sh it could change to this?
before_hook "check-git-clean" {
commands = ["apply", "plan"]
execute = ["terragrunt --check-git-clean"]
}
Not sure if there is any precedent for a terragrunt cli param that doesn't do normal terragrunt stuff but this would be pretty cool so that people wouldn't have to maintain their own check-git-clean.sh if all they want is basic best practice functionality?
If functionality becomes so common that we decide to build it directly into Terragrunt, we could also express it with a syntax like:
before_hook "check-git-clean" {
commands = ["apply", "plan"]
built_in = ["check-git-clean"]
}
Alternatively, we could have a collection of user-contributed "hooks" in a hooks folder in the Terragrunt repo, and you could just download the ones you need. Not sure what would work better. Probably worth adding hooks, seeing how people start using them, and to try to extract some best practices from that.
Would that be possible from a terragrunt workflow point of view that the before_hook is run before *.tfvars are parsed and injected in the temp folder with the terraform module files?
i'm thinking about resolving external references such as arns, ids, urls, secrets, etc. and injecting them in *.tfvars files through 3rd party templating tools.
# *.tfvars template file before before_hook
database_password = "${DATABASE_PASSWORD}"
some_other_key = "some_other_value"
envsubst them)# resolved *.tfvars file after before_hook
database_password = "some_password_fetch_by_hook_script"
some_other_key = "some_other_value"
terragrunt applyIn some cases, it's very hard to inject remote data sources outputs in terraform modules when one wants to use them nested in other resources' blocks.
It's easier to inject those refs directly at terragrunt *.tfvars files level. I currently deal with that with a custom script that prepares the main *.tfvars before I run terragrunt apply
Would that be possible from a terragrunt workflow point of view that the before_hook is run before *.tfvars are parsed and injected in the temp folder with the terraform module files?
Since the Terragrunt configuration, including hooks, are defined in a .tfvars file, we couldn't do it before parsing that file. But we could execute the hook before the files are copied. Or even have multiple stages: before_init, before, after.
i'm thinking about resolving external references such as arns, ids, urls, secrets, etc. and injecting them in *.tfvars files through 3rd party templating tools.
Ah, interesting use case. Actually, you'd probably want this hook to run after copying the .tfvars files so that the references are resolved in the tmp files and not the original source files. Otherwise, your source files would have modifications in them鈥攕uch as secrets鈥攖hat you might not want to accidentally check in!
Actually, you'd probably want this hook to run after copying the .tfvars files so that the references are resolved in the tmp files and not the original source files
yes, you're right 馃憤
Implemented in #439 and available in https://github.com/gruntwork-io/terragrunt/releases/tag/v0.14.4.
Is there a way to add an extra argument to "hooks" for "changing directory"/chdir before executing it?
Would be great, I am very very new to Go, but I could give it a go. Or is there already a proposal for something like that?
Regards.
Most helpful comment
If functionality becomes so common that we decide to build it directly into Terragrunt, we could also express it with a syntax like:
Alternatively, we could have a collection of user-contributed "hooks" in a
hooksfolder in the Terragrunt repo, and you could just download the ones you need. Not sure what would work better. Probably worth adding hooks, seeing how people start using them, and to try to extract some best practices from that.