github.com/hugorut/terraform@v1.1.3/website/docs/language/modules/develop/refactoring.mdx (about)

     1  ---
     2  page_title: Refactoring
     3  description: How to make backward-compatible changes to modules already in use.
     4  ---
     5  
     6  # Refactoring
     7  
     8  -> **Note:** Explicit refactoring declarations with `moved` blocks is available in Terraform v1.1 and later. For earlier Terraform versions or for refactoring actions too complex to express as `moved` blocks, you can
     9  use the [`terraform state mv` CLI command](/cli/commands/state/mv)
    10  as a separate step.
    11  
    12  In shared modules and long-lived configurations, you may eventually outgrow
    13  your initial module structure and resource names. For example, you might decide
    14  that what was previously one child module makes more sense as two separate
    15  modules and move a subset of the existing resources to the new one.
    16  
    17  Terraform compares previous state with new configuration, correlating by
    18  each module or resource's unique address. Therefore _by default_ Terraform
    19  understands moving or renaming an object as an intent to destroy the object
    20  at the old address and to create a new object at the new address.
    21  
    22  When you add `moved` blocks in your configuration to record where you've
    23  historically moved or renamed an object, Terraform treats an existing object at
    24  the old address as if it now belongs to the new address.
    25  
    26  > **Hands On:** Try the [Use Configuration to Move Resources](https://learn.hashicorp.com/tutorials/terraform/move-config) tutorial on HashiCorp Learn.
    27  
    28  ## `moved` Block Syntax
    29  
    30  A `moved` block expects no labels and contains only `from` and `to` arguments:
    31  
    32  ```hcl
    33  moved {
    34    from = aws_instance.a
    35    to   = aws_instance.b
    36  }
    37  ```
    38  
    39  The example above records that the resource currently known as `aws_instance.b`
    40  was known as `aws_instance.a` in a previous version of this module.
    41  
    42  Before creating a new plan for `aws_instance.b`, Terraform first checks
    43  whether there is an existing object for `aws_instance.a` recorded in the state.
    44  If there is an existing object, Terraform renames that object to
    45  `aws_instance.b` and then proceeds with creating a plan. The resulting plan is
    46  as if the object had originally been created at `aws_instance.b`, avoiding any
    47  need to destroy it during apply.
    48  
    49  The `from` and `to` addresses both use a special addressing syntax that allows
    50  selecting modules, resources, and resources inside child modules. Below, we
    51  describe several refactoring use-cases and the appropriate addressing syntax
    52  for each situation.
    53  
    54  * [Renaming a Resource](#renaming-a-resource)
    55  * [Enabling `count` or `for_each` For a Resource](#enabling-count-or-for_each-for-a-resource)
    56  * [Renaming a Module Call](#renaming-a-module-call)
    57  * [Enabling `count` or `for_each` For a Module Call](#enabling-count-or-for_each-for-a-module-call)
    58  * [Splitting One Module into Multiple](#splitting-one-module-into-multiple)
    59  * [Removing `moved` blocks](#removing-moved-blocks)
    60  
    61  ## Renaming a Resource
    62  
    63  Consider this example module with a resource configuration:
    64  
    65  ```hcl
    66  resource "aws_instance" "a" {
    67    count = 2
    68  
    69    # (resource-type-specific configuration)
    70  }
    71  ```
    72  
    73  Applying this configuration for the first time would cause Terraform to
    74  create `aws_instance.a[0]` and `aws_instance.a[1]`.
    75  
    76  If you later choose a different name for this resource, then you can change the
    77  name label in the `resource` block and record the old name inside a `moved` block:
    78  
    79  ```hcl
    80  resource "aws_instance" "b" {
    81    count = 2
    82  
    83    # (resource-type-specific configuration)
    84  }
    85  
    86  moved {
    87    from = aws_instance.a
    88    to   = aws_instance.b
    89  }
    90  ```
    91  
    92  When creating the next plan for each configuration using this module, Terraform
    93  treats any existing objects belonging to `aws_instance.a` as if they had
    94  been created for `aws_instance.b`: `aws_instance.a[0]` will be treated as
    95  `aws_instance.b[0]`, and `aws_instance.a[1]` as `aws_instance.b[1]`.
    96  
    97  New instances of the module, which _never_ had an
    98  `aws_instance.a`, will ignore the `moved` block and propose to create
    99  `aws_instance.b[0]` and `aws_instance.b[1]` as normal.
   100  
   101  Both of the addresses in this example referred to a resource as a whole, and
   102  so Terraform recognizes the move for all instances of the resource. That is,
   103  it covers both `aws_instance.a[0]` and `aws_instance.a[1]` without the need
   104  to identify each one separately.
   105  
   106  Each resource type has a separate schema and so objects of different types
   107  are not compatible. Therefore, although you can use `moved` to change the name
   108  of a resource, you _cannot_ use `moved` to change to a different resource type
   109  or to change a managed resource (a `resource` block) into a data resource
   110  (a `data` block).
   111  
   112  ## Enabling `count` or `for_each` For a Resource
   113  
   114  Consider this example module containing a single-instance resource:
   115  
   116  ```hcl
   117  resource "aws_instance" "a" {
   118    # (resource-type-specific configuration)
   119  }
   120  ```
   121  
   122  Applying this configuration would lead to Terraform creating an object
   123  bound to the address `aws_instance.a`.
   124  
   125  Later, you use [`for_each`](/language/meta-arguments/for_each) with this
   126  resource to systematically declare multiple instances. To preserve an object
   127  that was previously associated with `aws_instance.a` alone, you must add a
   128  `moved` block to specify which instance key the object will take in the new
   129  configuration:
   130  
   131  ```hcl
   132  locals {
   133    instances = tomap({
   134      big = {
   135        instance_type = "m3.large"
   136      }
   137      small = {
   138        instance_type = "t2.medium"
   139      }
   140    })
   141  }
   142  
   143  resource "aws_instance" "a" {
   144    for_each = local.instances
   145  
   146    instance_type = each.value.instance_type
   147    # (other resource-type-specific configuration)
   148  }
   149  
   150  moved {
   151    from = aws_instance.a
   152    to   = aws_instance.a["small"]
   153  }
   154  ```
   155  
   156  The above will keep Terraform from planning to destroy any existing object at
   157  `aws_instance.a`, treating that object instead as if it were originally
   158  created as `aws_instance.a["small"]`.
   159  
   160  When at least one of the two addresses includes an instance key, like
   161  `["small"]` in the above example, Terraform understands both addresses as
   162  referring to specific _instances_ of a resource rather than the resource as a
   163  whole. That means you can use `moved` to switch between keys and to add and
   164  remove keys as you switch between `count`, `for_each`, or neither.
   165  
   166  The following are some other examples of valid `moved` blocks that record
   167  changes to resource instance keys in a similar way:
   168  
   169  ```hcl
   170  # Both old and new configuration used "for_each", but the
   171  # "small" element was renamed to "tiny".
   172  moved {
   173    from = aws_instance.b["small"]
   174    to   = aws_instance.b["tiny"]
   175  }
   176  
   177  # The old configuration used "count" and the new configuration
   178  # uses "for_each", with the following mappings from
   179  # index to key:
   180  moved {
   181    from = aws_instance.c[0]
   182    to   = aws_instance.c["small"]
   183  }
   184  moved {
   185    from = aws_instance.c[1]
   186    to   = aws_instance.c["tiny"]
   187  }
   188  
   189  # The old configuration used "count", and the new configuration
   190  # uses neither "count" nor "for_each", and you want to keep
   191  # only the object at index 2.
   192  moved {
   193    from = aws_instance.d[2]
   194    to   = aws_instance.d
   195  }
   196  ```
   197  
   198  -> **Note:** When you add `count` to an existing resource that didn't use it,
   199  Terraform automatically proposes to move the original object to instance zero,
   200  unless you write an `moved` block explicitly mentioning that resource.
   201  However, we recommend still writing out the corresponding `moved` block
   202  explicitly, to make the change clearer to future readers of the module.
   203  
   204  ## Renaming a Module Call
   205  
   206  You can rename a call to a module in a similar way as renaming a resource.
   207  Consider the following original module version:
   208  
   209  ```hcl
   210  module "a" {
   211    source = "../modules/example"
   212  
   213    # (module arguments)
   214  }
   215  ```
   216  
   217  When applying this configuration, Terraform would prefix the addresses for
   218  any resources declared in this module with the module path `module.a`.
   219  For example, a resource `aws_instance.example` would have the full address
   220  `module.a.aws_instance.example`.
   221  
   222  If you later choose a better name for this module call, then you can change the
   223  name label in the `module` block and record the old name inside a `moved` block:
   224  
   225  ```hcl
   226  module "b" {
   227    source = "../modules/example"
   228  
   229    # (module arguments)
   230  }
   231  
   232  moved {
   233    from = module.a
   234    to   = module.b
   235  }
   236  ```
   237  
   238  When creating the next plan for each configuration using this module, Terraform
   239  will treat any existing object addresses beginning with `module.a` as if
   240  they had instead been created in `module.b`. `module.a.aws_instance.example`
   241  would be treated as `module.b.aws_instance.example`.
   242  
   243  Both of the addresses in this example referred to a module call as a whole, and
   244  so Terraform recognizes the move for all instances of the call. If this
   245  module call used `count` or `for_each` then it would apply to all of the
   246  instances, without the need to specify each one separately.
   247  
   248  ## Enabling `count` or `for_each` For a Module Call
   249  
   250  Consider this example of a single-instance module:
   251  
   252  ```hcl
   253  module "a" {
   254    source = "../modules/example"
   255  
   256    # (module arguments)
   257  }
   258  ```
   259  
   260  Applying this configuration would cause Terraform to create objects whose
   261  addresses begin with `module.a`.
   262  
   263  In later module versions, you may need to use
   264  [`count`](/language/meta-arguments/count) with this resource to systematically
   265  declare multiple instances. To preserve an object that was previously associated
   266  with `aws_instance.a` alone, you can add a `moved` block to specify which
   267  instance key that object will take in the new configuration:
   268  
   269  ```hcl
   270  module "a" {
   271    source = "../modules/example"
   272    count  = 3
   273  
   274    # (module arguments)
   275  }
   276  
   277  moved {
   278    from = module.a
   279    to   = module.a[2]
   280  }
   281  ```
   282  
   283  The configuration above directs Terraform to treat all objects in `module.a` as
   284  if they were originally created in `module.a[2]`. As a result, Terraform plans
   285  to create new objects only for `module.a[0]` and `module.a[1]`.
   286  
   287  When at least one of the two addresses includes an instance key, like
   288  `[2]` in the above example, Terraform will understand both addresses as
   289  referring to specific _instances_ of a module call rather than the module
   290  call as a whole. That means you can use `moved` to switch between keys and to
   291  add and remove keys as you switch between `count`, `for_each`, or neither.
   292  
   293  For more examples of recording moves associated with instances, refer to
   294  the similar section
   295  [Enabling `count` and `for_each` For a Resource](#enabling-count-or-for_each-for-a-resource).
   296  
   297  # Splitting One Module into Multiple
   298  
   299  As a module grows to support new requirements, it might eventually grow big
   300  enough to warrant splitting into two separate modules.
   301  
   302  Consider this example module:
   303  
   304  ```hcl
   305  resource "aws_instance" "a" {
   306    # (other resource-type-specific configuration)
   307  }
   308  
   309  resource "aws_instance" "b" {
   310    # (other resource-type-specific configuration)
   311  }
   312  
   313  resource "aws_instance" "c" {
   314    # (other resource-type-specific configuration)
   315  }
   316  ```
   317  
   318  You can split this into two modules as follows:
   319  
   320  * `aws_instance.a` now belongs to module "x".
   321  * `aws_instance.b` also belongs to module "x".
   322  * `aws_instance.c` belongs module "y".
   323  
   324  To achieve this refactoring without replacing existing objects bound to the
   325  old resource addresses, you must:
   326  
   327  1. Write module "x", copying over the two resources it should contain.
   328  1. Write module "y", copying over the one resource it should contain.
   329  1. Edit the original module to no longer include any of these resources, and
   330     instead to contain only shim configuration to migrate existing users.
   331  
   332  The new modules "x" and "y" should contain only `resource` blocks:
   333  
   334  ```hcl
   335  # module "x"
   336  
   337  resource "aws_instance" "a" {
   338    # (other resource-type-specific configuration)
   339  }
   340  
   341  resource "aws_instance" "b" {
   342    # (other resource-type-specific configuration)
   343  }
   344  ```
   345  
   346  ```hcl
   347  # module "y"
   348  
   349  resource "aws_instance" "c" {
   350    # (other resource-type-specific configuration)
   351  }
   352  ```
   353  
   354  The original module, now only a shim for backward-compatibility, calls the
   355  two new modules and indicates that the resources moved into them:
   356  
   357  ```hcl
   358  module "x" {
   359    source = "../modules/x"
   360  
   361    # ...
   362  }
   363  
   364  module "y" {
   365    source = "../modules/y"
   366  
   367    # ...
   368  }
   369  
   370  moved {
   371    from = aws_instance.a
   372    to   = module.x.aws_instance.a
   373  }
   374  
   375  moved {
   376    from = aws_instance.b
   377    to   = module.x.aws_instance.b
   378  }
   379  
   380  moved {
   381    from = aws_instance.c
   382    to   = module.y.aws_instance.c
   383  }
   384  ```
   385  
   386  When an existing user of the original module upgrades to the new "shim"
   387  version, Terraform notices these three `moved` blocks and behaves
   388  as if the objects associated with the three old resource addresses were
   389  originally created inside the two new modules.
   390  
   391  New users of this family of modules may use either the combined shim module
   392  _or_ the two new modules separately. You may wish to communicate to your
   393  existing users that the old module is now deprecated and so they should use
   394  the two separate modules for any new needs.
   395  
   396  The multi-module refactoring situation is unusual in that it violates the
   397  typical rule that a parent module sees its child module as a "closed box",
   398  unaware of exactly which resources are declared inside it. This compromise
   399  assumes that all three of these modules are maintained by the same people
   400  and distributed together in a single
   401  [module package](/language/modules/sources#modules-in-package-sub-directories).
   402  
   403  To reduce [coupling](https://en.wikipedia.org/wiki/Coupling_\(computer_programming\))
   404  between separately-packaged modules, Terraform only allows declarations of
   405  moves between modules in the same package. In other words, Terraform would
   406  not have allowed moving into `module.x` above if the `source` address of
   407  that call had not been a [local path](/language/modules/sources#local-paths).
   408  
   409  Terraform resolves module references in `moved` blocks relative to the module
   410  instance they are defined in. For example, if the original module above were
   411  already a child module named `module.original`, the reference to
   412  `module.x.aws_instance.a` would resolve as
   413  `module.original.module.x.aws_instance.a`. A module may only make `moved`
   414  statements about its own objects and objects of its child modules.
   415  
   416  If you need to refer to resources within a module that was called using
   417  `count` or `for_each` meta-arguments, you must specify a specific instance
   418  key to use in order to match with the new location of the resource
   419  configuration:
   420  
   421  ```hcl
   422  moved {
   423    from = aws_instance.example
   424    to   = module.new[2].aws_instance.example
   425  }
   426  ```
   427  
   428  ## Removing `moved` Blocks
   429  
   430  Over time, a long-lasting module may accumulate many `moved` blocks.
   431  
   432  Removing a `moved` block is a generally breaking change because any configurations that refer to the old address will plan to delete that existing object instead of move it. We strongly recommend that you retain all historical `moved` blocks from earlier versions of your modules to preserve the upgrade path for users of any previous version.
   433  
   434  If you do decide to remove `moved` blocks, proceed with caution. It can be safe to remove `moved` blocks when you are maintaining private modules within an organization and you are certain that all users have successfully run `terraform apply` with your new module version.
   435  
   436  If you need to rename or move the same object twice, we recommend documenting the full history
   437  using _chained_ `moved` blocks, where the new block refers to the existing block:
   438  
   439  ```hcl
   440  moved {
   441    from = aws_instance.a
   442    to   = aws_instance.b
   443  }
   444  
   445  moved {
   446    from = aws_instance.b
   447    to   = aws_instance.c
   448  }
   449  ```
   450  
   451  Recording a sequence of moves in this way allows for successful upgrades for
   452  both configurations with objects at `aws_instance.a` _and_ configurations with
   453  objects at `aws_instance.b`. In both cases, Terraform treats the existing
   454  object as if it had been originally created as `aws_instance.c`.