brown·field
denoting or relating to urban sites for potential building development that have had previous development on them: Compare with greenfield. "a contaminated brownfield site in the inner city"
As infrastructure engineers we rarely get the opportunity to work in greenfield sites, where we get to create everything in the latest tool of our choice, so sometimes we need to import things that were built elsewhere. Sometimes we need to deal with growth & move resources between terraform state files to reduce blast radius. The perceived wisdom in the terraform community is just to terraform import
or terraform state mv
our way to happiness. That's not something that scales or works well in environments where you don't have direct access & rely on tools & automation to run terraform/terragrunt for you.
We've developed a pattern of brownfield-aware terraform modules that use terragrunt hooks to import resources if they already exist, and allow terraform to create resources that don't already exist.
The before_hook
script import-resources-into-tfstate.sh
still requires intimate knowledge of the resources created by your module, but is kept alongside your tf files in the module.
This technique is particularly helpful when you're bulk migrating resources or have multiple environments where manually running terraform import
becomes monotonous or error prone.
Additionally, our implementation of the script supports apply
and plan
mode, in plan mode we report what would happen & an in apply mode we perform the imports, making it pipeline safe. In our case, PR's against a terragrunt repo get feedback on planned imports, and imports apply when the PR is merged and the pipeline run for the main branch.
We run multiple production deployments from the same terraform code base, this runs automatically for us in our terragrunt pipeline, reducing the TOIL involved in migrations and state split operations. It also helps reduce TOIL & handoff when working in environments we don't have direct access to.
Here's an example invocation of our module, in this case we're importing a resource group if it already exists.
include {
path = "global.hcl"
}
terraform {
source = "..//."
before_hook "import" {
commands = ["apply", "plan"]
execute = ["./import-resources-into-tfstate.sh", get_terraform_command()]
}
}
inputs = {
resource_group_name = "terratest-abcd"
location = "uksouth"
}
stop writing things you can't test
This module includes a terratest integration test, which creates a resource group & then runs terragrunt
so that the hook will import the group, terraform will fail if the resource group wasn't imported (as you can't create a duplicate group).
Makefile contains targets for formatting, updating docs & running tests, installing requires tools and running the terratest integration test.
make validate
terraform fmt, init, validate.make test
run terratest integration tests.make docs
update README.md via terraform-docsmake clean
clean up all of the .terraform & .terragrunt cachesmake install_tools_macos
install required tools via brew
- Demonstrate state migration (
terraform state rm
from one module, import into new module)
Name | Version |
---|---|
terraform | >= 0.13 |
azurerm | ~> 3.56 |
Name | Version |
---|---|
azurerm | 3.57.0 |
No modules.
Name | Type |
---|---|
azurerm_resource_group.main | resource |
Name | Description | Type | Default | Required |
---|---|---|---|---|
location | Azure location, az account list-locations |
string |
n/a | yes |
resource_group_name | The name of an existing resource group. | string |
n/a | yes |
Name | Description |
---|---|
name | n/a |