Are you incorporating Terraform for managing cloud infrastructure? Maybe you have existing resources you want to manage under Terraform. This means you need to use Terraform import, and this article will show you how to import using an Azure virtual network as an example.

To follow along with this tutorial, you’ll need:

Setting up the Azure Environment

To demonstrate how to import Azure resources into Terraform, you need an existing environment for deployment. The following main.tf deploys a resource group, a virtual network, and three subnets. For the simplicity of the tutorial, variables are defined in the main.tf file, and the configuration uses several hard-coded values, such as resource group name, virtual network name, and location.

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=2.61.0"
    }
  }
}
provider "azurerm" {
  features {}
}
variable "subnets" {
    type = map
    description = "List of subnet names and address prefixes"
}
resource "azurerm_resource_group" "rg" {
  name     = "terraformimport-rg"
  location = "westus2"
}
resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-webapp-westus2"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  address_space       = ["10.222.0.0/16"]
}
resource "azurerm_subnet" "snet" {
    for_each = var.subnets
    name = each.key
    resource_group_name = azurerm_resource_group.rg.name
    virtual_network_name = azurerm_virtual_network.vnet.name
    address_prefixes = [each.value]
}

The auzrerm_subnet resource uses a for_each meta-argument that iterates through the subnets map variable. The subnet name uses the map key while the address_prefixes uses the map value of each item.

The terraform.tfvars file contains a map of subnets for the subnets variable value. The subnets are snet-web, snet-app, and snet-db.

subnets = {
    "snet-web" = "10.222.10.0/24"
    "snet-app" = "10.222.15.0/24"
    "snet-db" = "10.222.20.0/24"
}

This tutorial does not cover Terraform deployments in-depth, but here are the commands to deploy this configuration. You will first need to authenticate out to Azure using the Azure CLI.

terraform init
terraform validate
terraform plan -out demo.tfplan
terraform apply demo.tfplan

The configuration should deploy a resource group (terraformimport-rg), virtual network (vnet-webapp-westus2), and three subnets (snet-web, snet-app, and snet-db).

viewing azure resources
Verifying Azure deployment from Terraform configuration

Modifying Azure Outside of Terraform

Once you’ve deployed resources using Terraform, the terraform.tfstate file is now your source of truth. The Terraform state file keeps track of your infrastructure and configuration. Terraform uses state to create plans and make infrastructure changes. Therefore, any future changes you make to the configuration should be performed using Terraform.

However, if an unknowing administrator makes a change that Terraform doesn’t know about, you can import these changes and bring them under Terraform management. For example, take the virtual network deployment from earlier. You decide to add a fourth subnet (snet-logs) but add it through the Azure portal instead of updating the Terraform configuration.

adding azure subnet to virtual network
Manually added subnet outside of Terraform

If you run terraform plan again, Terraform will not recognize that this subnet is outside its scope of management. Instead, Terraform will only compare what is deployed and what is defined in its configuration. So now you have an Azure resource that is outside of Terraform management.

Import Existing Azure Resources into Terraform

Luckily, Terraform can import existing Azure infrastructure to bring it under Terraform management. Using import, you can import resources into the state. Unfortunately, at this time, Terraform cannot generate a configuration. Because of this, you have to write the resource configuration block manually for the added resource.

To import a resource into the Terraform state:

  1. Modify the Terraform configuration and variable files as need with the new resource. In this example, you need to add the snet-logs subnet into the terraform.tfvars file, like so:
subnets = {
    "snet-web" = "10.222.10.0/24"
    "snet-app" = "10.222.15.0/24"
    "snet-db" = "10.222.20.0/24"
    "snet-logs" = "10.222.25.0/24"
}
  1. Run the terraform plan command again to get the address information for the resource to import. You need the address information in order to add it to the Terraform state file using the import command. You will not actually apply this plan like you did when setting up the environment.
  1. The plan command output will see the new subnet defined in the subnets variable is not in the current state. Terraform shows that it will add the new resource with the resource address. In this case, the resource address is azurerm_subnet.snet[“snet-logs]. Copy this value.
terraform plan azure
Using terraform plan to find the resource address
  1. Next, you need the resource ID of the snet-logs subnet. There are two ways to get this information.

Option 1: From the virtual network resource, go to Settings > Properties and copy the Resource ID value. Append “/subnets/snet-logs” to this value to represent the subnet.

azure virtual network resource id
Getting the Azure resource ID

Option 2: Open the current terraform.tfstate file and copy an existing subnet’s resource ID. Modify the subnet name to match the imported subnet.

  1. Use the terraform import command in PowerShell and specify the resource address from Step 3 and the resource ID from Step 4. Be sure to replace <subscription id> and <resource group> with values specific to your environment.

    Note: To allow PowerShell and Terraform to interpret the resource address correctly, enclose the string in single quotes, and escape the subnet double-quotes using backslashes. This syntax is specific when importing resources created using the for_each function. If you do not use this formatting, you will receive the following error:

    Index brackets must contain either a literal number or a literal string.

    You can see other examples in the Terraform documentation.
terraform import 'azurerm_subnet.snet[\"snet-logs\"]' /subscriptions/<subscript id>/resourceGroups/<resource group>/providers/Microsoft.Network/virtualNetworks/vnet-webapp-westus2/subnets/snet-logs
  1. Terraform will output the results of the import command. You can also open the terraform.tfstate file and verify the subnet is now listed (but don’t modify the state file manually!).
terraform import azure resources
Using terraform import with resource address and resource ID
  1. If you run the terraform plan command again, Terraform should show that nothing needs to be changed in the environment. This means Terraform did not detect any differences between your configuration and the existing resources.

Summary

Terraform isn’t just for greenfield deployments. Using Terraform import, you can bring existing resources and infrastructure under Terraform’s management. However, the current import process requires determining the resource address and resource ID, which can be tedious.

To see an example of importing lots of resources, check out Thomas Thornton’s article Importing Terraform state at scale in Azure.

Looking for more Azure content? Check out more of my Azure-focused articles!