/tfautomv

Generate Terraform moved blocks automatically for painless refactoring

Primary LanguageGoApache License 2.0Apache-2.0

tfautomv

License GitHub release Go Report Card

Generate Terraform moved blocks automatically.

Why?

tfautomv (a.k.a Terraform auto-move) is a refactoring helper. With it, making structural changes to your Terraform codebase becomes much easier.

When you move a resource in your code, Terraform loses track of the resource's state. The next time you run Terraform, it will plan to delete the resource it has memory of and create the "new" resource it found in your refactored code.

tfautomv inspects the output of terraform plan, detects such creation/deletion pairs and writes a moved block so that Terraform now knows no deletion or creation is required.

We explain why we built tfautomv in more detail in this blog article.

Here's a quick view of what tfautomv does:

demo

Requirements

tfautomv uses the Terraform CLI command under the hood. This allows it to work with any Terraform version reliably.

You will need Terraform v1.1 or above to generate moved blocks. Or Terraform v0.13 or above to generate terraform state mv commands.

Installation

Contributions to support other installation methods are welcome!

Homebrew

On MacOS or Linux:

brew install busser/tap/tfautomv

Yay

On Arch Linux:

yay tfautomv-bin

Shell script

On MacOS or Linux:

curl -sSfL https://raw.githubusercontent.com/busser/tfautomv/main/install.sh | sh

This script can probably support Windows with a small amount of work. Contributions welcome!

Download

On the Github repository's Releases page, download the binary that matches your workstation's OS and CPU architecture.

Put the binary in a directory present in your system's PATH environment variable.

From source

You must have Go 1.18+ installed to compile tfautomv.

Clone the repository and build the binary:

git clone https://github.com/busser/tfautomv
cd tfautomv
make build

Then, move bin/tfautomv to a directory resent in your system's PATH environment variable.

Usage

Generating moved blocks

In any directory where you would run terraform plan, you can run:

tfautomv

This will run terraform init, terraform plan, and then write moved blocks to a moves.tf file.

That's all there is to it!

Generating terraform state mv commands

If you are using a version of Terraform older than v1.1 or don't want to use moved blocks, you can generate terraform state mv commands instead:

tfautomv -output=commands

This will print commands to standard output. You can copy and paste them to a terminal to run them manually.

Alternatively, you can write the commands to a file:

tfautomv -output=commands > moves.sh

Or pipe them into a shell to run them immediately:

tfautomv -output=commands | sh

Understanding why a resource was not matched

If you are not seeing moved blocks for a resource you expected to be matched, you can run tfautomv with the -show-analysis flag to get more information:

tfautomv -show-analysis

This will print a detailed analysis of why each resource was or was not matched.

From there, you can choose to edit your code, write a moved block manually, or use the -ignore flag to ignore certain differences.

Ignoring certain differences

tfautomv works by comparing resources Terraform plans to create (those in your code) to those Terraform plans to delete (those in your state). Sometimes, tfautomv may not be able to match two resources together because of a difference in a specific attribute, even though the resources are in fact the same. This usually happens when the Terraform provider that manages the resource has transformed the attribute's value in some way.

In those cases, you can use the -ignore flag to ignore specific differences. tfautomv will ignore differences based on a set of rules that you can provide.

Each rule includes:

  • A kind that identifies the nature of the difference to ignore
  • A resource type the rule applies to
  • An attribute inside the resource the rule applies to
  • Optionally, additional arguments specific to the class

A rule is written as a colon-separated string:

<KIND>:<RESOURCE TYPE>:<ATTRIBUTE NAME>[:<KIND ARGUMENTS>]

You can use the -ignore flag multiple times to provide multiple rules:

tfautomv \
  -ignore="whitespace:azurerm_api_management_policy:xml_content" \
  -ignore="prefix:google_storage_bucket_iam_member:bucket:b/"

If you have a use case that is not covered by existing kinds, please open an issue so we can track demand for it.

The everything kind

Use the everything kind to ignore any difference between two values of an attribute:

tfautomv -ignore="everything:<RESOURCE TYPE>:<ATTRIBUTE>"

For example:

tfautomv -ignore="everything:random_pet:length"

The whitespace kind

Use the whitespace kind to ignore differences in whitespace between two values of an attribute:

tfautomv -ignore="whitespace:<RESOURCE TYPE>:<ATTRIBUTE NAME>"

For example, this rule:

tfautomv -ignore="whitespace:azurerm_api_management_policy:xml_content"

will allow these two resources to match:

# This resource has its XML nicely formatted.
resource "azurerm_api_management_policy" "foo" {
  api_management_id = "..."

  xml_content = <<-EOT
  <policies>
    <inbound>
      <cross-domain />
      <base />
      <find-and-replace from="xyz" to="abc" />
    </inbound>
  </policies>
  EOT
}

# This resource has its XML on one line.
resource "azurerm_api_management_policy" "bar" {
  api_management_id = "..."

  xml_content = "<policies><inbound><cross-domain /><base /><find-and-replace from=\"xyz\" to=\"abc\" /></inbound></policies>"
}

The prefix kind

Use the prefix kind to ignore a specific prefix between in one of two values of an attribute:

tfautomv -ignore="prefix:<RESOURCE TYPE>:<ATTRIBUTE NAME>:<PREFIX>"

For example:

tfautomv -ignore="prefix:google_storage_bucket_iam_member:bucket:b/"

will strip the b/ prefix from the bucket attribute of any google_storage_bucket_iam_member resources before comparing the attirbute's values.

Referencing nested attributes

Join parent attributes with child attributes with a .:

<KIND>:<RESOURCE TYPE>:parent_obj.child_field
<KIND>:<RESOURCE TYPE>:parent_list.0

If using the -show-analysis flag, you can see the full path to an attribute in the analysis output.

Passing additional arguments to Terraform

You can pass additional arguments to Terraform by using Terraform's built-in TF_CLI_ARGS and TF_CLI_ARGS_name environment variables..

For example, in order to use a file of variables during Terraform's plan:

TF_CLI_ARGS_plan="-var-file=production.tfvars" tfautomv

Or to skip Terraform's refresh and speed up the planning step:

TF_CLI_ARGS_plan="-refresh=false" tfautomv

Using Terragrunt instead of Terraform

You can tell tfautomv to use the Terragrunt CLI instead of the Terraform CLI with the -terraform-bin flag:

tfautomv -terraform-bin=terragrunt

This also works with any other executable that has an init and plan command.

Disabling colors in output

Add the -no-color flag to your tfautomv command to disable output formatting like colors, bold text, etc.

For example:

tfautomv -no-color

Thanks

Thanks to Padok, where this project was born 💜

License

The code is licensed under the permissive Apache v2.0 license. Read this for a summary.