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