tfblocks: small utility for working with Terraform migrations
If you haven’t been around the block (heh) in a while, you might’ve missed the introduction of the moved
(Dec ‘21), import
(Jun ‘23) and more recently removed
(Jan ‘24) blocks in Terraform. They have been great quality-of-life improvements that simplify refactoring and migrating Terraform code, and allow you to change resource names, import existing resources into your state, and remove resources from your state – all defined as code that you can predictably and safely apply as you would any other Terraform change. No more directly modifying state using the Terraform CLI1 and crossing your fingers that you didn’t mess anything up. Painful Terraform migrations are now a thing of the past!
… right? Well, not entirely.
The problem
Let’s say you want to move a hundred resources from one Terraform state to another. While creating the removed
blocks to remove the resources from the source state is quite straightforward, each import
block needed to import them into a target state requires an ID that uniquely represents each resource.
The format of the ID varies from resource type to resource type, and can only be found informally documented as examples in the provider documentation, or buried deep in the provider code.
Are IDs an Amazon Resource Name (ARN)? A specific resource attribute? Multiple attributes separated by hyphens, underscores, forward slashes and/or pipes? It depends!
So you might end up with a workflow like this:
- Run
terraform state show "<resource-address>"
to get the attributes of a given resource in your Terraform state. - Look up documentation on how to construct the ID from these attributes.
- Manually create each
import
block with the correct ID format. - Repeat 100 times 😩
As you can imagine, this can be quite error-prone and time-consuming.
A solution
So I wondered – could this be simplified? Do I know someone who can help me make sense of a lot of semi-structured data? I cloned the hashicorp/terraform-provider-aws repository, created a tiny local agent and had my friend Claude go to town on the AWS provider documentation, converting all import examples it could find into small Python classes. 10k LLM-generated code lines, some manual cleanup and testing later, and a simple tool emerged: tfblocks.
The selling-point of tfblocks is a large Python file containing import ID generators for all AWS resources that support importing. This is packaged into a little CLI that reads Terraform state from standard input, and outputs to standard output through a set of commands that can be convenient when working with migrations:
tfblocks import
- generateimport
blockstfblocks remove
- generateremoved
blockstfblocks list
- output resource addresses delimited by newlines
All of these commands support filtering to only generate output for the resources and/or files you are interested in.
Example workflow
Let’s say you’re moving a lot of resources between states. With tfblocks
, your workflow might look like this:
- In the root module for the source state, generate all the imports for the target state:
terraform show -json | tfblocks import > ../<target-root-module>/import.tf
- In the root module for the source state, generate all the removals for the source state:
terraform show -json | tfblocks remove > removed.tf
- Review the contents of the generated files, verify that the results of a
terraform plan
in both root modules look as expected, manually fix any errors, and off you go!
Bottom line
tfblocks is a very simple tool. It’s not groundbreaking, and it’s far from perfect. With that said, I think it can give you a much better starting point than writing these blocks from scratch, especially if you are migrating a large number of AWS resources. Over the last couple of weeks, it has personally saved me quite a lot of time when working on various migrations!
⚠️ Some words of caution:
- Always verify your Terraform plan before applying any generated code blocks! tfblocks is very experimental, and there are very likely bugs lurking in some of the ID formats.
- tfblocks currently2 only supports generating import IDs for AWS resources: For AWS resources that don’t support importing, or resources belonging to other providers, it will generate an
import
block but leave the ID as an empty string with a# TODO
comment.
Wanna give tfblocks a go? If you have uv
installed, here’s an example on how to use it (visit the README for more examples):
terraform show -json | uvx git+https://github.com/stekern/tfblocks tfblocks import
-
While tools like tfmigrate exist, they were built before the
moved
,removed
andimport
blocks were introduced, and they might not be necessary anymore. tfmigrate seems like a powerful tool, but it also introduces new concepts and new tooling into your workflow. I like to keep things vanilla when possible. ↩ -
While I might add support for other providers in the future, this functionality should ideally be handled by the providers themselves (as they are the source of truth for the ID format) and perhaps exposed directly in the Terraform state. Until that happens, tfblocks can help fill the gap. ↩