MCPcopy Index your code
hub / github.com/busser/tfautomv

github.com/busser/tfautomv @v0.7.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.7.0 ↗ · + Follow
184 symbols 700 edges 34 files 45 documented · 24%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

tfautomv

License GitHub release Go Report Card

Generate moved blocks and state move commands automatically for Terraform, OpenTofu, and Terragrunt.

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

Best Practices

tfautomv is designed for refactoring scenarios where you want to restructure your Terraform code without changing the actual infrastructure. Understanding this distinction is crucial for successful usage.

✅ Good use cases (pure refactoring)

  • Renaming resources: aws_instance.webaws_instance.web_server
  • Moving resources between modules: aws_instance.webmodule.ec2.aws_instance.web
  • Changing resource organization: Converting single resources to for_each loops
  • Module restructuring: Moving resources between different modules

❌ Problematic use cases (infrastructure changes)

  • Changing resource configuration: Removing or adding attributes like tags, security_groups, etc.
  • Combining refactoring with configuration changes: Renaming AND modifying attributes in the same operation
  • Provider-specific transformations: Changes where the provider modifies attribute values

Recommended workflow

  1. First, make structural changes only: Rename resources, move between modules, or restructure without changing any resource attributes
  2. Run tfautomv: Generate the appropriate moved blocks or terraform state mv commands
  3. Apply the moves: This should result in an empty or minimal plan showing no infrastructure changes
  4. Then make configuration changes: In a separate step, modify resource attributes as needed

This approach ensures that moves represent safe refactoring operations separate from infrastructure changes.

Requirements

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

Certain features require specific versions of Terraform:

  • moved blocks require Terraform v1.1 or above
  • cross-module terraform state mv commands require Terraform v0.14 or above
  • single-module terraform state mv commands require Terraform v0.13 or above

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

asdf

With asdf version manager:

asdf plugin add tfautomv https://github.com/busser/asdf-tfautomv.git

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

Quick Start

Basic usage - run in any directory where you would run terraform plan:

tfautomv

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

You can also target a specific working directory:

tfautomv ./production

Core Features

Generating moved blocks

By default, tfautomv generates moved blocks when possible:

tfautomv

Force moved blocks only with the --output=blocks flag:

tfautomv --output=blocks

Generating terraform state mv commands

Force terraform state mv commands only with the --output=commands flag:

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

The -o flag is shorthand for --output:

tfautomv -o commands

Finding moves across multiple directories

If you have multiple Terraform modules in different directories, you can pass those directories to tfautomv:

tfautomv ./production/main ./production/backup -o commands

This will run terraform init, terraform refresh, and terraform plan in each directory, and then write terraform state mv commands to standard output. These commands will move resources within and across directories as needed.

Terraform does not natively support moving resources across directories. To achieve this, tfautomv will output commands that pull copies of each directory's state, perform the moves, and then push the new state back to the directory's state backend.

You can pass as many directories as you want to tfautomv.

This is only compatible with the commands output format. Terraform's moved block syntax does not support moving resources across directories.

Advanced Features

Performance optimization

By default, tfautomv runs Terraform's init, refresh, and plan steps. To save time, you can skip the init or refresh steps with the --skip-init and --skip-refresh flags:

tfautomv --skip-init --skip-refresh

The -s flag is shorthand for --skip-init and -S for `--skip-refresh:

tfautomv -sS

Debugging and verbosity

If you are not seeing a moved block for a resource you expected to be moved, you can increase tfautomv's verbosity with the -v flag to get more information:

tfautomv -v

The default verbosity level is 0. You can increase the verbosity up to 3 by repeating the -v flag:

tfautomv -vvv

Alternatively, you can specify a specific verbosity level with the --verbosity flag:

tfautomv --verbosity=2

Based on why the resource was not moved, you can choose to edit your code, write a moved block manually, or use the -ignore flag to ignore certain differences.

level 0 (default) level 1 (-v) level 2 (-vv) level 3 (-vvv)
verbosity level 0 verbosity level 1 verbosity level 2 verbosity level 3

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.

⚠️ Important considerations when using --ignore

The --ignore flag tells tfautomv to act as if certain attributes don't exist when comparing resources. While powerful, this comes with risks:

  • Risk of incorrect matches: If ignored attributes are actually important for identifying the correct resource pairing, tfautomv may match unrelated resources or fail to find matches
  • Intended for provider quirks: Use primarily when providers transform attribute values in ways that don't reflect actual infrastructure changes
  • Not for configuration changes: Avoid using --ignore to force matches when you've intentionally changed resource configuration

Good use cases for --ignore: - Provider transforms whitespace in policy documents - Provider adds computed fields that weren't in the original configuration - Provider normalizes values (e.g., adding default ports to security group rules)

Problematic use cases for --ignore: - Forcing matches when you've intentionally changed tags, security groups, or other meaningful attributes - Ignoring differences that represent real infrastructure changes you made

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 .:

```plaintext :<RESOURC

Extension points exported contracts — how you extend this code

Rule (Interface)
A Rule allows tfautomv to equate certains attribute values that would normally be considered different.
pkg/engine/rule.go
Option (FuncType)
An Option configures how Terraform commands are run.
pkg/terraform/options.go

Core symbols most depended-on inside this repo

writeCode
called by 40
test/e2e/test_utils.go
Colorf
called by 35
pkg/pretty/color.go
ID
called by 20
pkg/engine/resource.go
countPlannedChanges
called by 20
test/e2e/test_utils.go
terraformPlan
called by 16
test/e2e/test_utils.go
Color
called by 15
pkg/pretty/color.go
terraformInitAndApply
called by 14
test/e2e/test_utils.go
runTfautomvPipeSh
called by 13
test/e2e/test_utils.go

Shape

Function 125
Method 44
Struct 12
FuncType 1
Interface 1
TypeAlias 1

Languages

Go100%

Modules by API surface

pkg/pretty/summary.go34 symbols
test/e2e/test_utils.go23 symbols
test/e2e/e2e_test.go16 symbols
pkg/terraform/options.go11 symbols
main.go10 symbols
pkg/terraform/move.go7 symbols
pkg/pretty/summary_test.go5 symbols
pkg/pretty/color.go5 symbols
pkg/engine/rules/whitespace.go5 symbols
pkg/engine/resource.go5 symbols
pkg/engine/plan.go5 symbols
pkg/terraform/plan.go4 symbols

For agents

$ claude mcp add tfautomv \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact