Importing Resources Into Terraform
Sometimes terraform apply
fails with the following error message:
Error: A resource with the ID "/subscriptions/****/resourceGroups/my-awesome-rg/providers/Microsoft.Something/exampleResource/my-resource" already exists - to be managed via Terraform this resource needs to be imported into the State. Please see the resource documentation for "azurerm_example_resource" for more information.
with azurerm_example_resource.my_resource,
on .components/foo/myfile.tf line 25, in resource "azurerm_example_resource" "my_resource":
25: resource "azurerm_example_resource"
This message can pop up for several reasons - most often it is because the resource identified by the error has been either created, re-created or modified outside of terraform. This means the object needs to be “imported” before terraform can perform certain operations on it. Thankfully there is an easy, self-serviceable way to do this that this document will detail.
The import
Block
Terraform 1.5.0 introduces the import block. This is a line of code that can be inserted into your terraform code to specify an externally managed resource that is to be brought into the terraform state. This is not to be confused with terraform import
, the command-line argument that performs the same operation. Compared to terraform import
, an import
block is easier and safer to execute, as it integrates seamlessly into existing terraform apply
stages in your pipeline and does not require manual tinkering with the state file.
After applying the import, the import block can be safely removed from your code. Attempting to import a resource into the same address again is a harmless no-op and should not affect your configuration.
Implementation
An import block can be inserted anywhere into your terraform code. You will need to know two variables for the block: the resource ID and the resource address. Both are output directly by the error message.
Resource ID
The resource ID is the unique identifier of the resource in Azure. It’s also part of the URL used to visit the ID in the azure portal. Some pipelines censor the subscription ID of the resource in the error message. You will need to know the full resource ID in order to import the resource. The subscription ID of the resource can more often than not be interpolated using terraform variables as will be shown in the example configuration later on.
Resource IDs will always take the following form:
/subscriptions/01234567-89ab-cdef-0123-456789abcdef/resourceGroups/my-awesome-rg/providers/Microsoft.Something/exampleResource/my-resource
#### Resource Address
The resource address is the internal identifier terraform uses to reference the resource in code. What this will look like will depend entirely on your configuration and where the error is occurring. If the error is occurring in a module (as it often does) the resource address may be significantly longer than the example below.
azurerm_example_resource.my_resource
#### Environment Filtering
Your error will be occurring in a specific subscription in a specific environment. As most pipelines throughout HMCTS are multi-environment, it will be necessary to restrict the import
block to the specific environment where the error is occurring. Failure to do this will mean that the pipeline will import the wrong resources when running in other environments.
#### Import Multiple Resources
Import blocks import a single resource. If there are multiple resources that require importing, you will need multiple import blocks. This can also be accomplished by clever use of a for_each but the example provided here will not be covering that.
## Example configuration
The following is an example import block that can be placed into your code. Take care to ensure that the resource ID and resource address are correct before running. A terraform plan
can be run to verify that the import block will function as expected before running apply
.
# Obtain the subscription ID of the problematic resource
data "azurerm_subscription" "current" {}
import {
# Run this import only in prod
for_each = var.env == "prod" ? toset(["import"]) : toset([])
# Specify the resource address to import to
to = azurerm_example_resource.my_resource
# Specify the resource ID to import from
id = "/subscriptions/${data.azurerm_subscription.current.subscription_id}/resourceGroups/my-awesome-rg/providers/Microsoft.Something/exampleResource/my-resource"
}