So you’d like to start using infrastructure as code, but have a bunch of existing Azure resources? Or maybe you’ve added some resources through UI and want to include them in existing terraform script?
terraform import
can help you with that (although not as much as it could).
Gameplan
The algorithm is rather straightforward:
- Define the desired resource in terraform, specifying only the required attributes.
- Run
terraform import
to map the resource to actual Azure one. - Run
terraform plan
to get a diff between terraform’s resource and Azure one. - Update terraform script to match the diff and go to point 3. Repeat points 3.-4. until terraform matches the Azure resource.
- Run
terraform apply
to update terraform state.
Case 1 - Resource Group
Let’s try this in practice. The simplest resource in Azure is a resource group.
1. Define terraform resource
Add resource definition:
# resource-group.tf
resource "azurerm_resource_group" "rg" {
name = "hmdev-sample-rg" # the name of your pre-existing resource group
location = "westeurope"
}
Your provider configuration may vary, but here’s a simple one for starters:
# provider.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=2.78.0"
}
}
}
provider "azurerm" {
features {}
}
Init first:
terraform init
2. Import
First, let’s try plan to see what’s terraform planning to do:
> terraform plan
Terraform will perform the following actions:
# azurerm_resource_group.rg will be created
+ resource "azurerm_resource_group" "rg" {
+ id = (known after apply)
+ location = "westeurope"
+ name = "hmdev-sample-rg"
}
Plan: 1 to add, 0 to change, 0 to destroy.
No surprise, terraform wants to create a new resource group - because it have that resource in its state, thus doesn’t know it exists.
We need to import it with terraform import {ADDR} {ID}
.
The command expects two arguments:
ADDR
is the resoirce address in terraform script (which is simply{resource_type}.{resource_name}
, i.e. ` azurerm_resource_group.rg`).ID
is Azure resourceId. How do I know the resource id? I go to portal.azure.com, and navigate to the specific resource. Then, in Overview section, there’s a little link called “JSON View”, which shows the Resource ID, along with all RM data.
> terraform import azurerm_resource_group.rg subscriptions/ddc49e36-1815-46a9-9c8a-fea443074404/resourceGroups/hmdev-sample-rg
Import successful!
The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
The import simply adds an entry to the state that tells terraform that resource azurerm_resource_group.rg from the script is mapped to that specific Azure resource.
It also gets the properties of the actual resource and puts them in the state file. (try doing terraform show
to see what’s in the state file).
3. Plan
Now, let’s try plan again:
> terraform plan
...
# azurerm_resource_group.rg will be updated in-place
~ resource "azurerm_resource_group" "rg" {
id = "/subscriptions/ddc49e36-1815-46a9-9c8a-fea443074404/resourceGroups/hmdev-sample-rg"
name = "hmdev-sample-rg"
~ tags = {
- "foo" = "bar" -> null
}
# (1 unchanged attribute hidden)
# (1 unchanged block hidden)
}
Ok, We’re getting somewhere. Now terraform recognizes the existing resource, but shows some differences. I’ve added a tag on the resource group (via portal) and terraform is telling me that this tag is not part of terraform’s resource definition and it will be removed.
4. Update. Repeat.
Let’s add the missing tag then:
resource "azurerm_resource_group" "rg" {
name = "hmdev-sample-rg"
location = "westeurope"
tags = {
"foo" = "bar"
}
}
Plan again… and voila:
> terraform plan
No changes. Your infrastructure matches the configuration.
5. Apply
Now I can safely do terraform apply
- the state will be updated and no changes to actual infra will be made.
Case 2 - Web App
Ok, we nailed the resource group, now let’s something more complicated - a Web App.
1. Define resource
Start again by adding a resource to terraform:
resource "azurerm_app_service_plan" "serviceplan" {
name = "hmdev-sample-free-plan"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
sku {
tier = "Standard"
size = "S1"
}
}
resource "azurerm_app_service" "sample-webapp" {
name = "hmdev-sample-webapp"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
app_service_plan_id = azurerm_app_service_plan.serviceplan.id
site_config {
}
app_settings = {
}
logs {
}
}
App service needs a service plan, so I added this as well. Fortunately, terraform docs include all the required pieces for a given resource type, so you don’t need to guess.
2. Import
Import the resources:
> terraform import azurerm_app_service_plan.serviceplan /subscriptions/ddc49e36-1815-46a9-9c8a-fea443074404/resourceGroups/hmdev-sample-rg/providers/Microsoft.Web/serverfarms/hmdev-sample-free-plan
> terraform import azurerm_app_service.sample-webapp /subscriptions/ddc49e36-1815-46a9-9c8a-fea443074404/resourceGroups/hmdev-sample-rg/providers/Microsoft.Web/sites/hmdev-sample-webapp
Note: Service Plan is simple enough, but webapp is a bit more tricky one. Some optional attributes might be ignored in state diff if you don’t define them. That’s why we have
site_config
,app_settings
andlogs
blocks defined.
3. Plan
Let’s see the plan:
> terraform plan
Terraform will perform the following actions:
# azurerm_app_service.sample-webapp will be updated in-place
~ resource "azurerm_app_service" "sample-webapp" {
~ app_settings = {
- "WEBSITE_NODE_DEFAULT_VERSION" = "~16" -> null
}
id = "/subscriptions/ddc49e36-1815-46a9-9c8a-fea443074404/resourceGroups/hmdev-sample-rg/providers/Microsoft.Web/sites/hmdev-sample-webapp"
name = "hmdev-sample-webapp"
# (14 unchanged attributes hidden)
~ logs {
~ detailed_error_messages_enabled = true -> false
# (1 unchanged attribute hidden)
# (2 unchanged blocks hidden)
}
~ site_config {
- use_32_bit_worker_process = true -> null
# (18 unchanged attributes hidden)
# (1 unchanged block hidden)
}
# (4 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
4. Update. Repeat.
Analyze the diff and update terraform script to match the real resource. Repeat that until the plan doesn’t contain any changes (or you’re happy with the planned changes).
6. Apply
Once you’re happy with the plan, apply it.
Summary
That’s it. The process is not complicated, but may require a few (sometimes tedious) iterations. Before doing the final apply
step, be sure to compare your script with the actual resource (i.e. in the portal) and look for any non-default settings - there might be things that terraform plan
won’t tell you about.