Upgrading to Terragrunt 0.19.x
Background
Terraform 0.12 was released in May, 2019, and it included a few major changes:
- More strict rules around what can go in a
.tfvars
file. In particular, any variable defined in a.tfvars
file that does not match a correspondingvariable
definition in your.tf
files produces an error. - A shift from HCL to HCL2 as the main syntax. This included support for first-class expressions (i.e., using variables
and functions without having to wrap everything in
${...}
).
Before version 0.19.0, Terragrunt had you define its configuration in a terragrunt = { ... }
variable in
a terraform.tfvars
file, but due to item (1) this no longer works with Terraform 0.12 and newer. That means we had to
move to a new file format. This requires a migration, which is unfortunate, but as a nice benefit, item (2)
gives us a nicer syntax and new functionality!
Migration guide
The following sections outline the steps you may need to take in order to migrate from Terragrunt <= v0.18.x to Terragrunt 0.19.x and newer:
Check out this PR in the terragrunt-infrastructure-live-example repo for an example of what the code changes look like.
Move from terraform.tfvars to terragrunt.hcl
Since Terraform 0.12 has more strict rules about what can go into terraform.tfvars
files, you now need to move your
Terragrunt configuration from terraform.tfvars
to a terragrunt.hcl
file, removing the terragrunt = { ... }
wrapping along the way.
For example, if you had the following in terraform.tfvars
:
terragrunt = { terraform { source = "git::git@github.com:foo/modules.git//frontend-app?ref=v0.0.3" extra_arguments "custom_vars" { commands = ["apply", "plan"] arguments = ["-var", "foo=42"] } } remote_state { backend = "s3" config = { bucket = "my-terraform-state" key = "${path_relative_to_include()}/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "my-lock-table" } }}
You would migrate this to terragrunt.hcl
as follows:
terraform { source = "git::git@github.com:foo/modules.git//frontend-app?ref=v0.0.3" extra_arguments "custom_vars" { commands = ["apply", "plan"] arguments = ["-var", "foo=42"] }}remote_state { backend = "s3" config = { bucket = "my-terraform-state" key = "${path_relative_to_include()}/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "my-lock-table" }}
Move input variables into inputs
When we were using terraform.tfvars
files for Terragrunt configuration, we were piggybacking on the fact that
Terraform automatically loads variables from tfvars
files to set variables
for our modules:
# Terragrunt configurationterragrunt = { terraform { # ... } remote_state { # ... }}# Input variables to set for your Terraform moduleinstance_type = "t2.micro"instance_count = 10
With the move to terragrunt.hcl
, we no longer get this behavior for free. However, Terragrunt can simulate this
behavior for you if you define your input variables by specifying inputs = { ... }
:
terraform { # ...}remote_state { # ...}# Input variables to set for your Terraform moduleinputs = { instance_type = "t2.micro" instance_count = 10}
Whenever you run a Terragrunt command, such as terragrunt apply
, Terragrunt will make these variables available to
your Terraform module as environment variables.
Use first-class expressions
Terraform 0.11 only allowed special behavior, such as function calls, using “interpolation syntax,” where you wrapped
the code with ${...}
. Terragrunt included a handful of functions you could call using interpolation syntax, but
only within the terragrunt = { ... }
block:
terragrunt = { terraform { extra_arguments "retry_lock" { # Using a function within interpolation syntax commands = "${get_terraform_commands_that_need_locking()}" arguments = ["-lock-timeout=20m"] } }}# Using interpolation syntax outside of the terragrunt config did NOT work beforefoo = "${get_env("FOO", "default")}"
Terraform 0.12 has moved to HCL2, which has first-class support for expressions. That means you can call functions
without having to wrap them in ${...}
. Terragrunt embraces HCL2, and thanks to HCL2’s nice parser, that means we not
only get first-class expressions, but we can also use those expressions everywhere in terragrunt.hcl
!
terraform { extra_arguments "retry_lock" { # Using a function within first-class expressions! commands = get_terraform_commands_that_need_locking() arguments = ["-lock-timeout=20m"] }}inputs = { # This now works with Terragrunt 0.19.x and newer! foo = get_env("FOO", "default")}
Check attributes vs blocks usage
HCL2 is more strict about the difference between attributes:
# Attributes use an equals sign before the curly bracefoo = { bar = "baz"}
And blocks:
# Blocks do not use equal signs before the curly bracefoo { bar = "baz"}
Since Terragrunt uses HCL2, we now have to be more strict with which parts of the Terragrunt configuration are attributes and which are blocks:
# terraform is a block, so make sure NOT to include an equals signterraform { source = "git::git@github.com:foo/modules.git//frontend-app?ref=v0.0.3" # extra_arguments is a block, so make sure NOT to include an equals sign extra_arguments "custom_vars" { commands = ["apply", "plan"] arguments = ["-var", "foo=42"] }}# remote_state is a block, so make sure NOT to include an equals signremote_state { backend = "s3" # config is an attribute, so an equals sign is REQUIRED config = { bucket = "foo" # s3_bucket_tags is an attribute, so an equals sign is REQUIRED s3_bucket_tags = { owner = "terragrunt integration test" name = "Terraform state storage" } # dynamodb_table_tags is an attribute, so an equals sign is REQUIRED dynamodb_table_tags = { owner = "terragrunt integration test" name = "Terraform lock table" } # accesslogging_bucket_tags is an attribute, so an equals sign is REQUIRED accesslogging_bucket_tags = { owner = "terragrunt integration test" name = "Terraform access log storage" } }}# include is a block, so make sure NOT to include an equals signinclude { path = find_in_parent_folders("root.hcl")}# dependencies is a block, so make sure NOT to include an equals signdependencies { paths = ["../vpc", "../mysql", "../redis"]}# Inputs is an attribute, so an equals sign is REQUIREDinputs = { instance_type = "t2.micro" instance_count = 10}
Rename a few built-in functions
Two built-in functions were renamed:
get_tfvars_dir()
is now calledget_terragrunt_dir()
.get_parent_tfvars_dir()
is now calledget_parent_terragrunt_dir()
.
Make sure to make the corresponding updates in your terragrunt.hcl
file!
Use older Terraform
Although it is not officially supported and not tested, it is still possible to use terraform<0.12 with terragrunt >=0.19.
Just install a different version of terraform into a directory of your choice outside of PATH
and specify path to the binary in terragrunt.hcl
as terraform_binary
, plus you need to lower the version check constraint:
terraform_binary = "~/bin/terraform-v11/terraform"terraform_version_constraint = ">= 0.11"