Skip to main content

Using Terraform Registry modules with Terragrunt

·3 mins
Cover image

In theory, you can make use of any modules from the Terraform registry with Terragrunt, allowing you to make use of common solutions and patterns to quickly get your resources set up. However in practice, there are a couple of gotchas to be aware of.

Let’s take the s3-bucket module as an example. To use it, we first reference it in our terraform block:

terraform {
  source = "tfr:///terraform-aws-modules/s3-bucket/aws?version=2.11.1"
}

And then provide our input block:

inputs = {
  bucket = local.bucket_name

  tags = {
    Created-by = "Terraform"
  }
}

However, if you were to run terragrunt plan with this you would get the following error:

ERRO[0002] Found remote_state settings in terraform/env/dev/bucket/terragrunt.hcl but no backend block in the Terraform code in terraform/env/dev/bucket/.terragrunt-cache/Bz5idB9p8TTxhN-U5VTJpJxwiVc/wwBidpH2QLJNZX9No52Qxoz4ITU. You must define a backend block (it can be empty!) in your Terraform code or your remote state settings will have no effect! It should look something like this:

terraform {
  backend "s3" {}
}

ERRO[0002] Unable to determine underlying exit code, so Terragrunt will exit with error code 1

This is because Terragrunt attempts to fill in your remote settings, and does this by overriding the backend block in the module. But most Terraform modules will not provide the empty block we need.

This can be fixed by making use of the generate block, like this:

generate "backend" {
  path      = "backend.tf"
  if_exists = "overwrite_terragrunt"
  contents  = <<EOF
# Module does not include this required block
terraform {
  backend "s3" {}
} 
EOF
}

This creates a backend.tf file with the empty block. If you look inside your .terragrunt cache directory, you’ll see this file being created.

The next error I got was this:

There are some problems with the configuration, described below.

The Terraform configuration must be valid before initialization so that
Terraform can determine which modules and providers need to be installed.
╷
│ Error: Duplicate required providers configuration
│
│   on versions.tf line 4, in terraform:
│    4:   required_providers {
│
│ A module may have only one required providers configuration. The required
│ providers were previously configured at provider.tf:12,3-21.
╵

ERRO[0002] 1 error occurred:
	* exit status 1

Again, looking in the .terragrunt cache directory I can see there is a required_providers block in the versions.tf file as part of the module, and this is a duplicate of the one I have provided in my root terragrunt.hcl. Although the versions are compatible, only one block is allowed.

Again, I can use the generate block to overwrite that file, and in this case I want it to be empty (although I add a comment for reference):

generate "versions" {
  path      = "versions.tf"
  if_exists = "overwrite"
  contents  = <<EOF
# Overwrite versions.tf from module which contains a duplicate `required_providers` block
EOF
}

Note how I set if_exists to overwrite, as I’m overwriting a file not created by Terragrunt.

Now, I don’t want to specify all of this each time I want to create an S3 bucket, so I can extract this out to another file, for example includes/s3.hcl:

# This sets up the S3 module from the Terraform registry so we can use it
# with Terragrunt.

terraform {
  source = "tfr:///terraform-aws-modules/s3-bucket/aws?version=2.11.1"
}

generate "versions" {
  path      = "versions.tf"
  if_exists = "overwrite"
  contents  = <<EOF
# Overwrite versions.tf from module which contains duplicate `required_providers` block
EOF
}

generate "backend" {
  path      = "backend.tf"
  if_exists = "overwrite_terragrunt"
  contents  = <<EOF
# Module does not include this required block
terraform {
  backend "s3" {}
} 
EOF
}

And then include that file, which simply merges the config as if it was defined in this file:

# Use the S3 module
include "s3" {
  path = find_in_parent_folders("includes/s3.hcl")
}

inputs = {
  bucket = local.bucket_name
}

We can now reuse this module whenever we want to create an S3 bucket.

Cover image from Unsplash.


Want great, practical advice on implementing data mesh, data products and data contracts?

In my weekly newsletter I share with you an original post and links to what's new and cool in the world of data mesh, data products, and data contracts.

I also include a little pun, because why not? 😅

(Don’t worry—I hate spam, too, and I’ll NEVER share your email address with anyone!)


Andrew Jones
Author
Andrew Jones
I build data platforms that reduce risk and drive revenue.