Terraform Moved Block

Brendan Thompson • 9 December 2022 • 6 min read

Have you ever had a Terraform resource that you thought would look the same through its whole lifecycle only to find that you now need to either rename it, move it into a module or maybe have multiple instances of it using for_each or count (ideally not count). If you have then you have probably encountered the pain that comes with that, as up until Terraform v1.1 you would have had to of performed surgery on state or sacrifice those resources to recreate. With the advent of Terraform v1.1 came the moved block, this allows us to instruct Terraform that a resource (or data source) has a new ID. Today we are going to step through that.

moved Block

Firstly we will look at how to turn a single instance of a particular instance into multiple. To begin with, we have our single scratch_string resource that is going to tell us what sound a Cat makes. When we first wrote this code we must have thought that was the only animal we would care about.

resource "scratch_string" "this" {
  in = "Animal: Cat, Sound: meow"
}

As you would expect the above code when we do a plan will result in the below:

Terraform will perform the following actions:

  # scratch_string.this will be created
  + resource "scratch_string" "this" {
      + id = (known after apply)
      + in = "Animal: Cat, Sound: meow"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Let's say we now introduce the following locals which have several animals and sounds, and we update our above scratch_string to make use of this map to produce the three animals.

locals {
  animals = {
    cat  = "meow"
    dog  = "woof"
    frog = "ribbit"
  }
}

resource "scratch_string" "this" {
  for_each = local.animals

  in = format("Animal: %s, Sound: %s", title(each.key), each.value)
}

This will yield us the following result wherein the initial scratch_string.this is going to be destroyed. Perhaps in your scenario, this is okay, in which case you're fine to run an apply!

Terraform will perform the following actions:

# scratch_string.this will be destroyed
# (because resource uses count or for_each)
- resource "scratch_string" "this" {
- id = "afa3123a-7074-11ed-9c2f-aacb117da4fa" -> null
- in = "Animal: Cat, Sound: meow" -> null
}
# scratch_string.this["cat"] will be created + resource "scratch_string" "this" { + id = (known after apply) + in = "Animal: Cat, Sound: meow" } # scratch_string.this["dog"] will be created + resource "scratch_string" "this" { + id = (known after apply) + in = "Animal: Dog, Sound: woof" } # scratch_string.this["frog"] will be created + resource "scratch_string" "this" { + id = (known after apply) + in = "Animal: Frog, Sound: ribbit" } Plan: 3 to add, 0 to change, 1 to destroy.

If we needed that resource to remain though and not be recreated then that is where we would use the moved block as it allows us to update the entry in state to point to the new resource. Let's take a look at what that code looks like.

moved {
  from = scratch_string.this
  to   = scratch_string.this["cat"]
}

It's rather simple! We are telling Terraform to update the scratch_string.this entry in state to instead become scratch_string.this["cat"]. As you can see from the below plan we are only going to add two resources and do nothing else. If you ask me that's pretty damn amazing!

Terraform will perform the following actions:

  # scratch_string.this has moved to scratch_string.this["cat"]
    resource "scratch_string" "this" {
        id = "afa3123a-7074-11ed-9c2f-aacb117da4fa"
        # (1 unchanged attribute hidden)
    }

  # scratch_string.this["dog"] will be created
  + resource "scratch_string" "this" {
      + id = (known after apply)
      + in = "Animal: Dog, Sound: woof"
    }

  # scratch_string.this["frog"] will be created
  + resource "scratch_string" "this" {
      + id = (known after apply)
      + in = "Animal: Frog, Sound: ribbit"
    }

Plan: 2 to add, 0 to change, 0 to destroy.

You might now be asking yourself, what happens to that moved block once you have run an apply, do you still need to keep it? The answer is no, no you don't need to retain it. The operation has been performed and the resource has been moved within state.

If you were to do this via the CLI you would run the below command once the code is updated with the for_each.

terraform state mv 'scratch_string.this' 'scratch_string.this["cat"]'

So why bother with the moved block, in a scenario where you have a really simple single resource that you're refactoring there probably isn't a big difference between the two. The advantage of the moved block in that scenario is more around traceability, your engineers will be able to see that at a point in time the resource(s) were renamed and maybe even a comment in the code! It becomes more useful when you're talking about modules that others are consuming, in that instance, you can rename resources or create child/sub-modules and that change is completely seamless to your consumers.

The same logic above can be applied to many other scenarios:

Closing out

So today we have looked at how we can use the moved block to change a single resource into multiple without having to recreate that resource. We also looked at how to do that with the terraform state mv command. Finally, we went through a few examples quickly on the moved block to move a resource from a root module into a module -simply reverse to do the opposite- and rename an existing resource.

Hopefully, this post has provided some insight into how the moved block works and how you can use it in your code.


Brendan Thompson

Principal Cloud Engineer

Discuss on Twitter