Terraform import{} Block

Posted on May 30, 2023 | By Andrei Buzoianu, Elif Samedin | 15 minutes read

Introduction

Terraform version 1.5.0-alpha20230504 (May 4, 2023) introduced a new import block type to provide the Terraform import functionality as a configuration-driven operation. No alterations have been made to the existing terraform import CLI command.

Then, HashiCorp took things to the next level with 1.5.0-beta1 (May 15, 2023) and enabled the capability to generate configuration for imported resources. Let’s dive into this thrilling new feature!

Use Cases

Amid writing this, we only have access to an early version of the import block feature, for which HashiCorp is actively soliciting user feedback to guide further development.

Import has now been transformed into a configuration-driven, schedulable action, and is processed as part of a standard plan. Executing the command terraform plan will provide a summary of the resources that Terraform has planned to import, in addition to any other changes to the plan.

What does configuration-driven mean in this context? The solution proposes a flow driven by a configuration file. These files will be using the same HCL language, the configuration language authored by HashiCorp. Any data transformation or curation processes will now have a single point of entry (the configuration file is the only operation that needs to be maintained to achieve the import functionality). Thus, this method provides a curated view of the current and desired states to ensure those are complete and meet the intended objective. Imagine a real case scenario, where resources are spread across multiple vendors and services. You’ll have different needs and use cases and a configuration-driven approach can simplify the standardization process.

The new flag -generate-config-out=PATH, added to terraform plan in 1.5.0-beta1, facilitates the templating of configuration when importing existing resources into Terraform. Working in conjunction with the import block, Terraform will generate the corresponding HCL configuration for any resource included in an import block that does not already possess any associated configuration, and write it to a new file at PATH. Before applying, you should take a moment to review the generated configuration and make it your own. Let’s take a moment to remember that this is a cutting-edge feature and you should adapt it anyway to your own project and needs.

tfenv

One may recall tfenv from one of our previous articles. tfenv is a widely-utilized Terraform version manager that facilitates the management of different versions of Terraform when working on multiple projects in parallel.

tfenv install

We are going to use tfenv to install Terraform version 1.5.0-beta2.

$ tfenv install 1.5.0-beta2
Installing Terraform v1.5.0-beta2
Downloading release tarball from https://releases.hashicorp.com/terraform/1.5.0-beta2/terraform_1.5.0-beta2_linux_amd64.zip
####################################################################################################################################################################################### 100.0%
Downloading SHA hash file from https://releases.hashicorp.com/terraform/1.5.0-beta2/terraform_1.5.0-beta2_SHA256SUMS
Downloading SHA hash signature file from https://releases.hashicorp.com/terraform/1.5.0-beta2/terraform_1.5.0-beta2_SHA256SUMS.72D7468F.sig
gpgv: Signature made Wed 24 May 2023 03:28:41 PM EEST
gpgv:                using RSA key 374EC75B485913604A831CC7C820C6D5CD27AB87
gpgv: Good signature from "HashiCorp Security (hashicorp.com/security) <security@hashicorp.com>"
Archive:  /tmp/tfenv_download.TKvKpZ/terraform_1.5.0-beta2_linux_amd64.zip
  inflating: ~/.tfenv/versions/1.5.0-beta2/terraform
Installation of terraform v1.5.0-beta2 successful. To make this your default version, run 'tfenv use 1.5.0-beta2'

tfenv use the latest beta

Setting the default version to 1.5.0-beta2:

$ tfenv use 1.5.0-beta2
Switching default version to v1.5.0-beta2
Default version (when not overridden by .terraform-version or TFENV_TERRAFORM_VERSION) is now: 1.5.0-beta2

Examples

In the following example configuration, Terraform manages the azurerm_resource_group resource with the azurerm provider.

Importing a Resource Group to tfstate

After setting the desired Terraform version, we’re ready to try out the import blocks by bringing an Azure Resource Group to the tfstate.

The following applies to the Resource Group Terraform resource:

resource "azurerm_resource_group" "this" {
  name     = "IMPORT"
  location = "North Europe"
}

The import block consists of two arguments: id and to, where id takes a string and to is a resource address. In our case, id is the Resource Group ID in Azure and to refers to the previously defined azurerm_resource_group resource.

import {
  id = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT"
  to = azurerm_resource_group.this
}

Running terraform plan will produce a summary of the resources Terraform intends to import, as well as any other plan revisions.

$ terraform plan 
azurerm_resource_group.this: Preparing import... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]
azurerm_resource_group.this: Refreshing state... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]

Terraform will perform the following actions:

  # azurerm_resource_group.this will be imported
    resource "azurerm_resource_group" "this" {
        id       = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT"
        location = "northeurope"
        name     = "IMPORT"
        tags     = {}

        timeouts {}
    }

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

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
$ terraform apply 
azurerm_resource_group.this: Preparing import... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]
azurerm_resource_group.this: Refreshing state... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]

Terraform will perform the following actions:

  # azurerm_resource_group.this will be imported
    resource "azurerm_resource_group" "this" {
        id       = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT"
        location = "northeurope"
        name     = "IMPORT"
        tags     = {}

        timeouts {}
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

azurerm_resource_group.this: Importing... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]
azurerm_resource_group.this: Import complete [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]

Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.

The Resource Group was successfully imported:

$ terraform state list 
azurerm_resource_group.this

Once you’ve imported a resource, you may be tempted to delete the import block. However, keeping it in the configuration is totally harmless from Terraform’s perspective - it’s set to no-op, which means it literally does nothing after the resource has been imported. Nevertheless, there is of course OCD, or in our case CDO, which is basically the same thing, just in alphabetical order, like they should be…

$ terraform plan 
azurerm_resource_group.this: Refreshing state... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

In version 1.5.0-beta1, a new feature was introduced which enables the creation of configurations for imported resources. This functionality works alongside the import block and simplifies the process of configuring templates when importing existing resources into Terraform. To use this feature, you can use the terraform plan command with the additional flag -generate-config-out=PATH. When this is set, Terraform generates HCL configurations for any resource within an import block that lacks related configuration and saves it to a new file at the specified PATH. Prior to applying the changes, it is important to review the generated configuration and make any necessary changes.

Let’s now delete the azurerm_resource_group resource definition and try importing it to tfstate.

import {
  id = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT"
  to = azurerm_resource_group.this
}
$ terraform plan -generate-config-out=generated.tf
azurerm_resource_group.this: Preparing import... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]
azurerm_resource_group.this: Refreshing state... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]

Terraform will perform the following actions:

  # azurerm_resource_group.this will be imported
  # (config will be generated)
    resource "azurerm_resource_group" "this" {
        id       = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT"
        location = "northeurope"
        name     = "IMPORT"
        tags     = {}

        timeouts {}
    }

Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.
│ Warning: Config generation is experimental
│ Generating configuration during import is currently experimental, and the generated configuration format may change in future versions.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Terraform has generated configuration and written it to generated.tf. Please review the configuration and edit it as necessary before adding it to version control.

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
$ cat generated.tf 
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.

# __generated__ by Terraform from "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT"
resource "azurerm_resource_group" "this" {
  location = "northeurope"
  name     = "IMPORT"
  tags     = {}
  timeouts {
    create = null
    delete = null
    read   = null
    update = null
  }
}
$ terraform apply 
azurerm_resource_group.this: Preparing import... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]
azurerm_resource_group.this: Refreshing state... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]

Terraform will perform the following actions:

  # azurerm_resource_group.this will be imported
    resource "azurerm_resource_group" "this" {
        id       = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT"
        location = "northeurope"
        name     = "IMPORT"
        tags     = {}

        timeouts {}
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

azurerm_resource_group.this: Importing... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]
azurerm_resource_group.this: Import complete [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]

Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.

Importing a VNET & a SNET to tfstate

Let’s now extend the previous example and import to tfstate a Virtual Network and a Subnet which we have created beforehand in the IMPORT Resource Group.

resource "azurerm_resource_group" "this" {
  name     = "IMPORT"
  location = "North Europe"
}

import {
  id = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT"
  to = azurerm_resource_group.this
}

import {
  id = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET"
  to = azurerm_virtual_network.this
}
$ terraform plan -generate-config-out=generated.tf
azurerm_virtual_network.this: Preparing import... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET]
azurerm_virtual_network.this: Refreshing state... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET]
azurerm_resource_group.this: Refreshing state... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]

Planning failed. Terraform encountered an error while generating this plan.

│ Warning: Config generation is experimental
│ Generating configuration during import is currently experimental, and the generated configuration format may change in future versions.
│ Error: expected flow_timeout_in_minutes to be in the range (4 - 30), got 0
│   with azurerm_virtual_network.this,
│   on generated.tf line 5:
(source code not available)
│ Error: Value for unconfigurable attribute
│   with azurerm_virtual_network.this,
│   on generated.tf line 9:
(source code not available)
│ Can't configure a value for "subnet.0.id": its value will be decided automatically based on the result of applying this configuration.
│ Error: Value for unconfigurable attribute
│   with azurerm_virtual_network.this,
│   on generated.tf line 9:
│   (source code not available)
│ Can't configure a value for "subnet.1.id": its value will be decided automatically based on the result of applying this configuration.

When attempting to generate the configuration of these resources, terraform plan displays a number of errors. It’s important to keep in mind that this feature is still in its experimental stages, so it’s not surprising that we’re seeing this kind of behavior. Let’s keep exploring and learning from it!

$ cat generated.tf 
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.

# __generated__ by Terraform
resource "azurerm_virtual_network" "this" {
  address_space           = ["10.0.0.0/16"]
  bgp_community           = null
  dns_servers             = []
  edge_zone               = null
  flow_timeout_in_minutes = 0
  location                = "northeurope"
  name                    = "IMPORT-VNET"
  resource_group_name     = "IMPORT"
  subnet = [{
    address_prefix = "10.0.0.0/24"
    id             = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET/subnets/default"
    name           = "default"
    security_group = ""
    }, {
    address_prefix = "10.0.1.0/24"
    id             = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET/subnets/IMPORT-SNET"
    name           = "IMPORT-SNET"
    security_group = ""
  }]
  tags = {}
  timeouts {
    create = null
    delete = null
    read   = null
    update = null
  }
}

Conversely, when we specify the configuration of the resources to be imported into tfstate, Terraform is able to import them effectively, as evidenced by the summary generated by the terraform plan command.

resource "azurerm_resource_group" "this" {
  name     = "IMPORT"
  location = "North Europe"
}

resource "azurerm_virtual_network" "this" {
  name                = "IMPORT-VNET"
  resource_group_name = azurerm_resource_group.this.name
  location            = azurerm_resource_group.this.location
  address_space       = ["10.0.0.0/16"]

  subnet {
    name           = "IMPORT-SNET"
    address_prefix = "10.0.1.0/24"
  }
}

import {
  id = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT"
  to = azurerm_resource_group.this
}

import {
  id = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET"
  to = azurerm_virtual_network.this
}

Let’s see the execution plan, which lets us preview the changes that Terraform plans to make to our infrastructure:

$ terraform plan 
azurerm_resource_group.this: Refreshing state... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]
azurerm_virtual_network.this: Preparing import... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET]
azurerm_virtual_network.this: Refreshing state... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # azurerm_virtual_network.this will be updated in-place
  # (imported from "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET")
  ~ resource "azurerm_virtual_network" "this" {
        address_space           = [
            "10.0.0.0/16",
        ]
        dns_servers             = []
        flow_timeout_in_minutes = 0
        guid                    = "9ded08d3-254e-43f0-a034-772120038756"
        id                      = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET"
        location                = "northeurope"
        name                    = "IMPORT-VNET"
        resource_group_name     = "IMPORT"
      ~ subnet                  = [
          - {
              - address_prefix = "10.0.0.0/24"
              - id             = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET/subnets/default"
              - name           = "default"
              - security_group = ""
            },
          - {
              - address_prefix = "10.0.1.0/24"
              - id             = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET/subnets/IMPORT-SNET"
              - name           = "IMPORT-SNET"
              - security_group = ""
            },
          + {
              + address_prefix = "10.0.1.0/24"
              + id             = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET/subnets/IMPORT-SNET"
              + name           = "IMPORT-SNET"
            },
        ]
        tags                    = {}

      - timeouts {}
    }

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

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

And now, apply those changes:

$ terraform apply 
azurerm_resource_group.this: Refreshing state... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT]
azurerm_virtual_network.this: Preparing import... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET]
azurerm_virtual_network.this: Refreshing state... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # azurerm_virtual_network.this will be updated in-place
  # (imported from "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET")
  ~ resource "azurerm_virtual_network" "this" {
        address_space           = [
            "10.0.0.0/16",
        ]
        dns_servers             = []
        flow_timeout_in_minutes = 0
        guid                    = "9ded08d3-254e-43f0-a034-772120038756"
        id                      = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET"
        location                = "northeurope"
        name                    = "IMPORT-VNET"
        resource_group_name     = "IMPORT"
      ~ subnet                  = [
          - {
              - address_prefix = "10.0.0.0/24"
              - id             = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET/subnets/default"
              - name           = "default"
              - security_group = ""
            },
          - {
              - address_prefix = "10.0.1.0/24"
              - id             = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET/subnets/IMPORT-SNET"
              - name           = "IMPORT-SNET"
              - security_group = ""
            },
          + {
              + address_prefix = "10.0.1.0/24"
              + id             = "/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET/subnets/IMPORT-SNET"
              + name           = "IMPORT-SNET"
            },
        ]
        tags                    = {}

      - timeouts {}
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

azurerm_virtual_network.this: Importing... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET]
azurerm_virtual_network.this: Import complete [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET]
azurerm_virtual_network.this: Modifying... [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET]
azurerm_virtual_network.this: Modifications complete after 7s [id=/subscriptions/********-****-****-****-************/resourceGroups/IMPORT/providers/Microsoft.Network/virtualNetworks/IMPORT-VNET]

Apply complete! Resources: 1 imported, 0 added, 1 changed, 0 destroyed.

Final Thoughts

Importing a resource is now a configuration-driven action that is handled as part of a standard plan. This early version of the feature is a thrilling addition and we can’t wait for the release of Terraform 1.5.0 to see it in its full glory!

References