TL;DR: Move one thing at a time and don’t leave the state behind!

Refactoring: form, not function

As with any refactoring, the focus in on the form - not the function. Your goal is changing the structure of the code while preserving the current behavior.

In the context of infrastructure configuration, preserving the current behavior means no change in the actual infrastructure resources. In the context of Terraform, that means ensuring your plan has no changes (via terraform plan -detailed-exitcode).

However, it is not always simple to avoid changes in behavior when refactoring Terraform configuration.

Terraform ate my infra

Let’s say you make a code change to rename a resource from bucket_test to log_storage. All Terraform knows about is that bucket_test is gone and log_storage has come. For this reason, when asked to apply that change, it will faithfully delete bucket_test and create a new log_storage.

Even though unintended (and potentially catastrophic), such change in behavior are not rare: it’s easy to forget that the Terraform state is also part of your configuration.

In order to avoid the change in behavior you have to make a corresponding change in the state with terraform state mv.