Error: Cross-package move statement

In terraform 1.1.0 , it was announced that there was a new moved{} block to enable refactoring.

However, it seems that if you want to refactor into a module managed outside of the current repo, this is not allowed - and not mentioned anywhere.

Error: Cross-package move statement

  on .terraform/modules/example/main.tf line 4:
   4: moved {

This statement declares a move to an object declared in external module
package "git::ssh://git@github.com/user/terraform-module.git". Move
statements can be only within a single module package.

However, this seems like a very artificial limitation. terraform state rm/terraform import works just fine going to external modules. External modules may very well be managed by the same team, just in separate repos. Plan files also don’t seem to have any real difference between modules in the same repo and in separate ones.

Only being able to have auditable refactoring within the same repo is quite useless to the challenges I’m facing here. Is there a way to say “let me move to external modules anyways”, or a timeline for this limitation to be removed?

2 Likes

Hi @rgooler,

The following paragraphs in the documentation above moving between modules discuss this current restriction:

The multi-module refactoring situation is unusual in that it violates the typical rule that a parent module sees its child module as a “closed box”, unaware of exactly which resources are declared inside it. This compromise assumes that all three of these modules are maintained by the same people and distributed together in a single module package.

To reduce coupling between separately-packaged modules, Terraform only allows declarations of moves between modules in the same package. In other words, Terraform would not have allowed moving into module.x above if the source address of that call had not been a local path.

In other words, declaring that something moved into a child module declared elsewhere would impose a compatibility constraint on that other module without it being recorded in that module, and thus maintainers of that module would need to preserve compatibility without necessarily knowing that there’s something to preserve compatibility with.

If you are in control of both modules and in control of making sure that the caller switches to the new version of the called module at the right time for the move to make sense then you can use terraform state mv to tell Terraform to complete the move at that point, just as before.

The requirement for this to be allowed would be to have some new syntax for an external module to declare that it will accept a move from outside to a particular address, so that there is some record in the called module that there is an external interface that other modules may be relying on. That extra feature wasn’t part of the scope of the first phase of this feature, but it’s a likely future enhancement.

However, in this desire to reduce coupling, several tradeoffs are made without the user’s ability to override them.

  • terraform state mv is a highly-privileged operation which can cause friction in any environment where PRs are accepted and automated - but direct state manipulation is restricted.
  • Users may wish to transition responsibilities into another repository - owned locally or externally - and then clean up afterwards
  • terraform state mv doesn’t leave an audit log of infrastructure changes in the code repository
  • These artificial barriers vanish for anyone using a monorepo and git submodules for third party modules. If this coupling issue is more important than users making their own decisions over their own environments, then why is this restriction only applicable under some repo configurations?

Additionally, the proposed solution of creating a bidirectional approval process means that I, the user, am not permitted to migrate my dodgy code into a community supported module UNLESS I’m willing and able to modify state files directly, which is an action that is very difficult to peer review. This seems antithetical to the concept of refactoring in the first place.

If you want to make this a guardrail to prevent certain decisions from being made, then I believe that a few changes should be made:

  1. Improve the error message to link to documentation describing the coupling issue. The multi-module refactoring situation (especially as it relates to externally hosted modules) is not clearly laid out.
  2. Allow a force = True option. Operationally, even if I shouldn’t do this, sometimes I have to do bad practice to keep the site online. Additionally, for anyone using Terraform along with a tool such as OPA - this creates an easy point to say “Hey, force = True in a moved{} block requires approval”.
  3. Consider, when enhancing this and any other refactoring features - that part of the goal for users is to have tools that help them get out of a bad place. Having to bail out to running the command line tools breaks the concept of “infrastructure as code”, as its all code… except for this bit which was someone applying duct tape and hoping they didn’t make a mistake.

I think that moved is an amazing feature to have, but unfortunately these limits reduce it from being an extremely powerful tool to a tool that is all but unusable for the needs of myself and the users I support.

2 Likes

Thanks for the feedback, @rgooler!

The first iteration of this set of features is intended for a more “intentional” sort of refactoring where a single person is changing both the source and the destination in a single VCS commit as part of a carefully-planned refactoring, rather than for the slightly different scenario you seem to be targeting of digging out of a “messy” situation.

No new feature can possibly meet all use-cases in its first iteration, or it would basically never ship, but we do hope to learn from experiences and feedback like this about the initial feature and cautiously expand the scope of this feature over time. As we do so, we need to make sure that we don’t inadvertently create new burdens for the maintainers of shared modules (by turning what was previously implementation detail into a public interface they must be compatible with, without their consent) and make sure that the new features are hard to use incorrectly in a way that could cause inadvertent distruption to infrastructure.

That does mean that for now not all use-cases can be met by this feature, and so those use-cases will need to continue to be handled as before using terraform state mv. That doesn’t mean that’ll be the case forever, but it is the case for the first iteration in the v1.1 series.

I would also love this to work when using “remote” module sources. We manage our entire ecosystem of modules, so we have complete control over how they are used and would like to be able to have a “force” option or for it to just be a first class citizen.