github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/website/docs/language/modules/develop/refactoring.html.md (about)

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