Menu

Virtual Geek

Tales from real IT system administrators world and non-production environment

Mastering Terraform Import: Seamlessly Integrate Existing Azure Resources

Why Importing Matters in Infrastructure-as-Code
When adopting Terraform for existing cloud environments, you'll inevitably face a critical challenge: How to bring existing infrastructure under Terraform management? This is where resource importing becomes essential. Without proper imports, Terraform attempts to recreate resources that already exist, leading to errors and potential configuration conflicts.

The New Import Block: Introduced in Terraform 1.5+
Terraform 1.5 brought a game-changing feature to the table with its new import block, revolutionizing how we handle existing resources. This declarative approach lets you seamlessly import resources into your state file, ditching the need for complex CLI commands. Here's how it supercharges your workflow.

I will start with below basic terraform provider block of azurerm.

terraform {
    required_providers {
        azurerm = {
            source = "hashicorp/azurerm"
            version = ">=4.31.0"
        }
    }
}

provider "azurerm" {
    features {}
}

Just to verify I am using correct version I checked it first and next initialized provider.

Check my earlier email on same topic:
Importing existing resources into Terraform - Step by Step
Importing already created module Infrastructure into Terraform and update state file

Terraform main.tf import resources microsoft azure terraform initialization required provider block features azurerm init initialization version windows_amd64 infrastructure as a code cli hashicorp IBM lock.hcl source version.png

Let's start with a simple example. In my Azure portal, manually I've already created a resource group named 'test' and added a single resource - a Storage Account. In the next step, I'll demonstrate how to import these resources into a Terraform configuration file using the new import block, eliminating the need for complex terraform import command-line interactions.

terraform devops configuration file microsoft azure portal test resource group storage account blob storage s3 bucket infrastructure as a code configuration auto delete plan apply destory import configuration file monitoring.png

If you try to create a resource using Terraform that already exists in the Azure portal, the plan phase will complete successfully. However, when you attempt to apply the configuration, you'll encounter an error. This is because Terraform needs to import the existing resources into its state file before managing them.

# Added new configuration in tf file

resource "azurerm_resource_group" "rg" {
    name = "test"
    location = "East US"
}

--------------------------------------------------------------

Error: A resource with the ID "subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test" already exists - to be managed via Terraform this resource needs to be imported into the State. Please see the resource documenations for "azurerm_resource_group" for more information

terraform apply --auto-approve

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_resource_group.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "eastus"
      + name     = "test"
    }

Plan: 1 to add, 0 to change, 0 to destroy.
azurerm_resource_group.rg: Creating...
╷
│ Error: A resource with the ID "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test" already exists - to be managed via Terraform this resource needs to be imported into the State. Please see the resource documentation for "azurerm_resource_group" for more information.
│
│   with azurerm_resource_group.rg,
│   on main.tf line 14, in resource "azurerm_resource_group" "rg":
│   14: resource "azurerm_resource_group" "rg" {

Terraform microsoft resources required providers azurerm features azurerm_resource_group source version terraform apply --auto-approve environment variable ARM_SUBSCRIPTION_ID imported inot state block configuration file automation.png

To resolve this issue, you'll need to import the existing resources into your Terraform configuration using an import block. The import block requires two arguments: id and to.
To obtain the resource ID, you can use either the Azure CLI command: az resource list --resource-group <resourcegroupname> --query "[].id" --out tsv or the powershell command Get-AzResource -ResourceGroupName <resourcegroupname> | Select-Object -ExpandProperty ResourceId.

In the to parameter, specify the corresponding resource name, which will be used as the resource block name in your Terraform configuration.

import {
  id = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test" #ResourceID
  to = azurerm_resource_group.rg 
}

import {
  id = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test/providers/Microsoft.Storage/storageAccounts/vcloudlabcom" #ResourceID
  to = azurerm_storage_account.sa
}

------------------------------------------------------------------------------------------

$rg = 'test'
az resource list --resource-group $rg --query "[].id" --out tsv
Get-AzResource -ResourceGroupName $rg | Select-Object -ExpandProperty ResourceId 

Hashicorp terraform import block existing resources subscriptions provider microsoft storage account resource group az resource lit query get-azresource resourceid tsv using configuration files generate infrastructure as a code.png

In the screenshot below, I'm verifying my Terraform configuration files before running terraform plan. Currently, I have a single configuration file, main.tf, which contains:
A provider block for Azure Resource Manager (azurerm)

  • Two import blocks for the resource group and storage account
  • To import and generate configurations for these existing resources, I'll run the command:

terraform plan --generate-config-out=existingresources.tf

This generates a new file, existingresources.tf, containing the configuration for the resource group and storage account. You can see the updated file list after running the ls or dir command.
Note that this configuration generation feature is currently experimental.

terraform plan --generate-config-out=existingresources.tf                                                                                             
azurerm_resource_group.rg: Preparing import... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test]
azurerm_storage_account.sa: Preparing import... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test/providers/Microsoft.Storage/storageAccounts/vcloudlabcom]
azurerm_resource_group.rg: Refreshing state... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test]
azurerm_storage_account.sa: Refreshing state... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test/providers/Microsoft.Storage/storageAccounts/vcloudlabcom]

Terraform planned the following actions, but then encountered a problem:

  # azurerm_resource_group.rg will be imported
  # (config will be generated)
    resource "azurerm_resource_group" "rg" {
        id         = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test"
        location   = "eastus"
        managed_by = null
        name       = "test"
        tags       = {}
    }

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.
╵
╷
│ Error: expected blob_properties.0.change_feed_retention_in_days to be in the range (1 - 146000), got 0
│
│   with azurerm_storage_account.sa,
│   on existingresources.tf line 29:
│   (source code not available)

terraform infrastrucute as a code configuration files terraform import plan  --generate-config-out config generation experimental change feed retention in days tf files existingresource.tf microsoft azure.png

Although an error occurred during generation, I can resolve it by manually enabling blob change feed on the Storage Account. To do this, I'll configure the change feed logs to be deleted after a specified number of days, setting the retention period to a value between 1 and 146000 days.

Terraform Microsoft Azure Storage Account Data Protection Enabled blob change feed keep all logscontainers files shares queues table configuration file error hashicorp infrasturucture production.png

Upon opening the newly generated existingresources.tf file, you'll find resource blocks for azurerm_resource_group and azurerm_storage_account. To tidy up the code, you can remove unnecessary empty or null parameters. I'll also remove non-mandatory arguments and parameters, as per Terraform documentation.

Currently, the values are hardcoded in the generated configuration. To improve flexibility, you can create variables for these values. As shown in the screenshot, I've already fixed the change_feed_retention_days value and highlighted the arguments that can be removed as they're not required or changed from the configuration as per requirement.

Terraform Microsoft Azure subscription configuration files azurerm_resource group storage_account storagev2 import example existing resource null nfv3 sftp account kind tier replication_type dns public network blob container.png

In this final step, I'll apply the new Terraform configuration, which will import and add the existing resources to the Terraform state file. With this, my Terraform configuration code is now production-ready and accurately reflects the current state of my infrastructure.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
terraform apply --auto-approve
azurerm_resource_group.rg: Preparing import... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test]
azurerm_storage_account.sa: Preparing import... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test/providers/Microsoft.Storage/storageAccounts/vcloudlabcom]
azurerm_resource_group.rg: Refreshing state... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test]
azurerm_storage_account.sa: Refreshing state... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test/providers/Microsoft.Storage/storageAccounts/vcloudlabcom]

Terraform will perform the following actions:

  # azurerm_resource_group.rg will be imported
    resource "azurerm_resource_group" "rg" {
        id         = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test"
        location   = "eastus"
        managed_by = null
        name       = "test"
        tags       = {}
    }

  # azurerm_storage_account.sa will be imported
    resource "azurerm_storage_account" "sa" {
        access_tier                        = "Hot"
        account_kind                       = "StorageV2"
        account_replication_type           = "LRS"
        account_tier                       = "Standard"
        allow_nested_items_to_be_public    = false
        allowed_copy_scope                 = null
        cross_tenant_replication_enabled   = false
        default_to_oauth_authentication    = false
        dns_endpoint_type                  = "Standard"
        edge_zone                          = null
        https_traffic_only_enabled         = true
        id                                 = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test/providers/Microsoft.Storage/storageAccounts/vcloudlabcom"
        infrastructure_encryption_enabled  = false
        is_hns_enabled                     = false
        large_file_share_enabled           = true
        local_user_enabled                 = true
        location                           = "eastus"
        min_tls_version                    = "TLS1_2"
        name                               = "vcloudlabcom"
        nfsv3_enabled                      = false
        primary_access_key                 = (sensitive value)
        primary_blob_connection_string     = (sensitive value)
        primary_blob_endpoint              = "https://vcloudlabcom.blob.core.windows.net/"
        primary_blob_host                  = "vcloudlabcom.blob.core.windows.net"
        primary_blob_internet_endpoint     = null
        primary_blob_internet_host         = null
        primary_blob_microsoft_endpoint    = null
        primary_blob_microsoft_host        = null
        primary_connection_string          = (sensitive value)
        primary_dfs_endpoint               = "https://vcloudlabcom.dfs.core.windows.net/"
        primary_dfs_host                   = "vcloudlabcom.dfs.core.windows.net"
        primary_dfs_internet_endpoint      = null
        primary_dfs_internet_host          = null
        primary_dfs_microsoft_endpoint     = null
        primary_dfs_microsoft_host         = null
        primary_file_endpoint              = "https://vcloudlabcom.file.core.windows.net/"
        primary_file_host                  = "vcloudlabcom.file.core.windows.net"
        primary_file_internet_endpoint     = null
        primary_file_internet_host         = null
        primary_file_microsoft_endpoint    = null
        primary_file_microsoft_host        = null
        primary_location                   = "eastus"
        primary_queue_endpoint             = "https://vcloudlabcom.queue.core.windows.net/"
        primary_queue_host                 = "vcloudlabcom.queue.core.windows.net"
        primary_queue_microsoft_endpoint   = null
        primary_queue_microsoft_host       = null
        primary_table_endpoint             = "https://vcloudlabcom.table.core.windows.net/"
        primary_table_host                 = "vcloudlabcom.table.core.windows.net"
        primary_table_microsoft_endpoint   = null
        primary_table_microsoft_host       = null
        primary_web_endpoint               = "https://vcloudlabcom.z13.web.core.windows.net/"
        primary_web_host                   = "vcloudlabcom.z13.web.core.windows.net"
        primary_web_internet_endpoint      = null
        primary_web_internet_host          = null
        primary_web_microsoft_endpoint     = null
        primary_web_microsoft_host         = null
        public_network_access_enabled      = true
        queue_encryption_key_type          = "Service"
        resource_group_name                = "test"
        secondary_access_key               = (sensitive value)
        secondary_blob_connection_string   = (sensitive value)
        secondary_blob_endpoint            = null
        secondary_blob_host                = null
        secondary_blob_internet_endpoint   = null
        secondary_blob_internet_host       = null
        secondary_blob_microsoft_endpoint  = null
        secondary_blob_microsoft_host      = null
        secondary_connection_string        = (sensitive value)
        secondary_dfs_endpoint             = null
        secondary_dfs_host                 = null
        secondary_dfs_internet_endpoint    = null
        secondary_dfs_internet_host        = null
        secondary_dfs_microsoft_endpoint   = null
        secondary_dfs_microsoft_host       = null
        secondary_file_endpoint            = null
        secondary_file_host                = null
        secondary_file_internet_endpoint   = null
        secondary_file_internet_host       = null
        secondary_file_microsoft_endpoint  = null
        secondary_file_microsoft_host      = null
        secondary_location                 = null
        secondary_queue_endpoint           = null
        secondary_queue_host               = null
        secondary_queue_microsoft_endpoint = null
        secondary_queue_microsoft_host     = null
        secondary_table_endpoint           = null
        secondary_table_host               = null
        secondary_table_microsoft_endpoint = null
        secondary_table_microsoft_host     = null
        secondary_web_endpoint             = null
        secondary_web_host                 = null
        secondary_web_internet_endpoint    = null
        secondary_web_internet_host        = null
        secondary_web_microsoft_endpoint   = null
        secondary_web_microsoft_host       = null
        sftp_enabled                       = false
        shared_access_key_enabled          = true
        table_encryption_key_type          = "Service"
        tags                               = {}

        blob_properties {
            change_feed_enabled           = true
            change_feed_retention_in_days = 7
            default_service_version       = null
            last_access_time_enabled      = false
            versioning_enabled            = false

            container_delete_retention_policy {
                days = 7
            }

            delete_retention_policy {
                days                     = 7
                permanent_delete_enabled = false
            }
        }

        queue_properties {
            hour_metrics {
                enabled               = false
                include_apis          = false
                retention_policy_days = 0
                version               = "1.0"
            }
            logging {
                delete                = false
                read                  = false
                retention_policy_days = 0
                version               = "1.0"
                write                 = false
            }
            minute_metrics {
                enabled               = false
                include_apis          = false
                retention_policy_days = 0
                version               = "1.0"
            }
        }

        share_properties {
            retention_policy {
                days = 7
            }
        }
    }

Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.
azurerm_resource_group.rg: Importing... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test]
azurerm_resource_group.rg: Import complete [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test]
azurerm_storage_account.sa: Importing... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test/providers/Microsoft.Storage/storageAccounts/vcloudlabcom]
azurerm_storage_account.sa: Import complete [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/test/providers/Microsoft.Storage/storageAccounts/vcloudlabcom]

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

Conclusion: Why This Matters

The new import block fundamentally changes how we adopt Terraform in existing environments:

Declarative approach - Manage imports within configuration files
Auditability - Track imports via version control
Safety - Prevent accidental resource recreation
Team collaboration - Simplify onboarding to existing infrastructure

While the configuration generation is still experimental, this workflow dramatically reduces the manual effort required to bring existing Azure resources under Terraform management. By following this process, you'll achieve true infrastructure-as-code nirvana - where every resource is defined, versioned, and managed through Terraform.

Pro Tip: Always test imports in a non-production environment first!

Useful Articles
Create an Azure virtual machine scale set and load balancer using Terraform
Azure Terraform fixed Availibility Zones on Virtual Machine Scale Set
Writing and Using Terraform modules
Terraform Using one module variable in another module
Hashicorp Terraform dynamic block with example
Terraform for_each loop on map example
Terraform for_each loop on resource example
Terraform manage similar resources with for_each loop inside modules
Importing existing resources into Terraform - Step by Step
Importing already created module Infrastructure into Terraform and update state file
Conditionally create resources in Terraform

Go Back

Comment

Protected by Mathcha

Blog Search

Page Views

1 4 7 9 7 9 4 9

Archive

Follow me on Blogarama