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