How to bring existing Azure resources under Terraform management

In a perfect world, an application with all of its components is provisioned and managed as code from the very beginning. But unfortunately that is not always the reality. Some companies start to provision services manually before they find out this might not be a good thing for the long run. Others start with one tool, only to find out that they feel more comfortable with another tool – and want to change it. If you are using Hashicorp’s Terraform to manage your infrastructure, you can bring existing resources that have been provisioned outside of Terraform under its control. Read on to find out how.

Scenario

For this scenario I provisioned two resources manually in Azure, a SQL server resource and a SQL database resource.

Azure resources

High-Level Process

Using the Terraform CLI, you can import existing infrastructure and bring in under Terraform management. This means that you need to instruct Terraform, that there are some existing resources living somewhere on a platform it does not yet know about. To make Terraform aware of these resources, they need to be added to the Terraform state. Talking about state: Think of a database with a list of resources, properties, relationships etc. that maps everything it knows to real-world resources, such as the two SQL resources in this example. More info about state is available here.

The full process to bring existing resources under Terraform control looks like this:

  1. Collect data about the existing resources (optional)
  2. Create a Terraform configuration file
  3. Import resource details to Terraform state
  4. Test

Collect details about the existing resources (optional)

The first step is optional, but it can help to understand the processes and the existing resources a bit better. I start from scratch and want Terraform to read information about the two resources I want to bring under Terraform management. For that I create a brand new Terraform configuration file that will just read and output data from the two resources.

provider "azurerm" {
    features {}
}

data "azurerm_sql_server" "sqlserver1" {
  resource_group_name   = "t-rgr-sql-01"    
  name                  = "t-qsr-server-01"
}

data "azurerm_sql_database" "sqldatabase1" {
  resource_group_name   = "t-rgr-sql-01"
  server_name           = "t-qsr-server-01"
  name                  = "database01"
}

output "sqlserver1" {
  value                 = data.azurerm_sql_server.sqlserver1 
}

output "sqldatabase1" {
  value                 = data.azurerm_sql_database.sqldatabase1 
}
  • Provider block: This is used to describe how the Azure provider will be used. I have no special needs here.
  • Data blocks: Those are used to connect to existing resources and read their configurations. I specify the resource type (e.g. “azurerm_sql_server”, an Id I can later use to reference to the collected resource details, the resource group name and the resource name. To get the database I also need to specify the SQL server name.
  • Output blocks: The make the collected data visible, I use two output blocks that reference to the resources collected in the data blocks. With that the collected details will be shown after successful execution.

The configuration file would normally be optimized to use variables and references, but to ensure best readability I hard-coded everything in this code fragment.

Running “terraform apply” will connect to Azure, collect the data out output the details. It will not change anything because we have not specified any resource configurations. The output helps us to better understand the resources and property values the we will need in the next step.

Terraform output

Create a Terraform Configuration File

Now as we have some more insights into the resources and understand them better, it’s time to create the configuration file. You can either change the existing one from the first step or create a new one. The Terraform configuration file must be written so it can provision and manage the two resources with all relevant properties. As we collected everything in step 1, we can use most of the property names and values from there. Keep in mind that some properties are read-only or empty – you need to be selective what you want to add to the configuration file.

provider "azurerm" {
    features {}
}

resource "azurerm_sql_server" "sqlserver1" {
  name                        = "t-qsr-server-01"
  resource_group_name         = "t-rgr-sql-01"
  location                    = "westeurope"
  administrator_login         = "sqladm23791"
  administrator_login_password= "lkjLKJ23!!sdf234!"
  version                     = "12.0"
  tags = {
    Owner                     = "Marcel Zehner"
    CostCenter                = 400
  }
}

resource "azurerm_sql_database" "sqldatabase1" {
  name                        = "database01"
  resource_group_name         = "t-rgr-sql-01"
  server_name                 = "t-qsr-server-01"
  collation                   = "SQL_Latin1_General_CP1_CI_AS"
  location                    = "westeurope"
  edition                     = "Basic"
  read_scale                  = false
  tags = {
    Owner                     = "Marcel Zehner",
    CostCenter                = 400
  }
}

Again: This is not how a configuration file should look like, it’s a simple, easy-to-read example. One would use variables, references and (super important) never use clear-text credentials/passwords in a configuration file. But as this is not the purpose of this blog post I will keep this simple.

Now there is a last, very important step that we need to take care of before we can apply this configuration.

Import Resource Details to Terraform State

After creating the configuration file you could apply it, however, Terraform would think that those two resources need to be created – because it does not understand that they exist already. By using our configuration file and the “terraform import” command we can now import these resources properly into the Terraform state.

terraform import terraform_id azure_resource_id
  • terraform_id: This is the Terraform internal resource id I assigned in the configuration file. The format is <RESOURCETYPE>.<ID>.
  • azure_resource_id: This is the resource id of the resource in the Azure platform using. The format is /SUBSCRIPTIONS/SUBSCRIPTIONID/RESOURCEGROUPS/RESOURCEGROUPID/PROVIDERS/PROVIDERNAME/… You can get that value either from the output in step 1 or from the Azure portal (navigate to the resource, normally this value is visible in the properties section)

With those values, Terraform will be able to map a resource in the configuration file to a real-world resource. Now let’s import the two resources.

terraform import "azurerm_sql_server.sqlserver1" "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/t-rgr-sql-01/providers/Microsoft.Sql/servers/t-qsr-server-01"
terraform import "azurerm_sql_database.sqldatabase1" "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/t-rgr-sql-01/providers/Microsoft.Sql/servers/t-qsr-server-01/databases/database01"

The output of the commands should show a successful import.

Import successful

If you are using a local Terraform state file you can now open it (terraform.tfstate) and you should see the imported resources. If you use remote state and have access to it, open it from there. The resources should now be visible.

State file content

 

Test

By running “terraform apply”, Terraform will check the current state and compare it to the configuration. Depending on your exact configuration file, Terraform might find out that nothing needs to be changed. It understands that the two resources already exist now. If you added some additional properties, it might want to change the existing resources. But it should never try to create the two resources (if so, something went wrong).

Of course you can follow your regular process here by creating and saving an execution plan, with “terraform plan” before you apply it to the platform.

Your resources are now under Terraform management and can be maintained as if they were originally created by Terraform. Cool, but a definitely some time investment, especially if you have a large environment.

Cheers,
Marcel

About Marcel Zehner

Microsoft Azure MVP
This entry was posted in Azure, DevOps and tagged , , , , , . Bookmark the permalink.

5 Responses to How to bring existing Azure resources under Terraform management

  1. Neil M says:

    I would suggest that to test this out, no one runs Terraform “Apply” first. Use Terraform “Plan” first, then think about running Apply once you see the Plan output

    • “terraform apply” also creates an execution plan that you can analyze first in an interactive way. But I agree that you normally create, save and analyze an execution plan first. I added a note to the blog post. Thanks for the feedback!

  2. Thiago Beier says:

    great article – just tried with 0.12.28 version didn’t work – I’ll get back to this week with time and fresh mind to see what I’m doing that’s wrong – I have a test resource group with 2 windows vms2 with availability set and vnet – simple deployment (rds, vnet, sql : demo resource group) that I’d like to clone to diff. azure subscriptions using terraform and not ARM templates. 🙂

    • Strange, but give it a second try. Work on the first resource to see if it imports properly, then check if behaves under TF management as expected. Then continue with the next resource.

  3. Pingback: How to release existing resources from Terraform management | marcelzehner.ch

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s