TrademarkTrademark
Features
Documentation

Updating Terraform State, with Great Power comes Great Responsibility

Understand how to work with Terraform state more effectively.
Brendan ThompsonFebruary 14, 2023
Updating Terraform State, with Great Power comes Great Responsibility
Key takeaways
  • Manipulating Terraform state directly is usually not recommended since that is the job of your Terraform code, but it is sometimes unavoidable or useful for testing scenarios.
  • The terraform state command has seven subcommands: list, mv, pull, push, replace-provider, rm, and show; list and show are used most often to inspect what is in state.
  • Use taint to force recreation of a single resource on the next apply, terraform import to bring a manually created resource into state, and mv to move a resource into a module or a set.
  • As a rule of thumb, if you are modifying a state file by hand you are doing something wrong, and state manipulation should cover only about 5 percent of cases.

Now that we understand what Terraform State is, how do we manipulate it, and when should we? Most of the time you shouldn't touch state at all, since keeping it correct is the job of your Terraform code. But sometimes you don't have a choice, or you want to test out a particular scenario. That's when state manipulation comes into play.

First let's look at the terraform state CLI command. It comes with 7 subcommands:

  • list - list all resources within the state file
  • mv - move an item within the state file
  • pull - pull the current state file and output it to our terminal
  • push - update remote state from a local state file
  • replace-provider - replace a provider within the state file
  • rm - remove a resource from our state file
  • show - show a single resource within the state file

Generally I use list and show fairly frequently to interrogate what's in the state file, especially if it is a new codebase that I am looking at.

The rm subcommand can also be very useful if someone has been unkind enough to remove a resource (e.g. from the Azure portal). This allows us to remove that reference in state so that Terraform won't complain, although if we don't also remove it from the code, Terraform will try to create it on the next apply.

Here are some scenarios and how we would resolve them:

Force replacement/recreation of a single resource within state

This comes up fairly often during development on larger projects. Say you've built an entire cloud environment and now you want to recreate the network. taint lets you do that. The alternatives are deleting the resource by hand or commenting the code out, and neither is a good option.

 
# Find the resource in state
terraform state list
 
# Inspect the resource
terraform state show azurerm_virtual_network.this
 
# Taint the resource
terraform taint azurerm_virtual_network.this

On the next terraform apply the azurerm_virtual_network.this will be recreated. If you change your mind and no longer require the resource to be tainted then you can use the aptly named untaint command.

terraform untaint azurerm_virtual_network.this

Import an existing resource into our state file

Sometimes we define all of our resources in our Terraform code but for some reason the resource is manually created. This leaves us in a state where Terraform can see the resource on the remote end and in its code but not in state. In order to fix that we need to import the resource into state. This is where the import command comes into play. (For the full, modern import workflow, including import {} blocks, code generation, and bulk imports, see the Terraform import guide.)

As an example we are going to import the above virtual network into our state file.

terraform import azurerm_virtual_network.this /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/Resource_Group/providers/Microsoft.Network/virtualNetworks/Virtual Network

As you can see from the above the import process is pretty simple, we simply need to pass in the address in Terraform state and the ID that is used for the resource. This will be different between providers.

Move a resource from a single resource into a set of resources, or into a module

This happens as our Terraform code grows. We might start with a single instance of something and then move to multiple instances through the use of the for expression or through creation of a module. To tell Terraform that we want to move our currently existing resource into one of those constructs we can either use the moved block or the Terraform mv command. Here's an example.

 
# `for` expression
terraform mv azurerm_virtual_network.this azurerm_virtual_network.this["primary"]
 
# Module
terraform mv azurerm_virtual_network.this module.virtual_networks.azurerm_virtual_network.this

Update your state with the latest from your live resources

In this instance we want to force our state to go and check what the real state of our resources is and update state with any changes. This can be done with a very simple command.

terraform refresh

Closing out

This post covered the basics of working with Terraform state: tainting and untainting a resource, moving a resource to a new address, importing existing resources, and refreshing state from the resource APIs. There's more you can do, but this is about as much as you should be doing in state for 95% of cases. As a general rule of thumb, if you're editing a state file by hand, you're doing something wrong.

About the author
Brendan Thompsonsolutions engineer at Scalr
Brendan Thompson is a solutions engineer at Scalr, specializing in Terraform and cloud infrastructure.