Tutorials
Tutorials
January 11, 2024

Getting Started with the Azure Terraform Export Tool

By
Brendan Thompson

As I am sure many of us encountered a situation where we want to manage our existing infrastructure using Terraform however there is always the quandary of transforming that infrastructure into Terraform code. Thankfully there are a few tools out there that make this process very simple through the use of automation. Today we will be looking at aztfexport by Microsoft, this tool helps easily bring your existing Azure resources under Terraform management.

The below diagram shows the Azure infrastructure that we will be exporting from Azure with aztfexport. It is a very simple setup of just a single resource group containing a storage account and the container app environment with a single container app deployed as well as a default log analytics workspace.

Example of export from a resource group
Example of export from a resource group

Let's start the process of generating our resources and then importing them into state with aztfexport.

1. Firstly we need to execute the following command aztfexport rg rg-aue-dev-tf. This indicates to aztfexport that we are going to be exporting all resources within the rg-aue-dev-tf resource group on Azure

2. We will be presented with a Text User Interface (TUI) which shows the process of importing items, once the items have been identified the following interface will be shown.

Example of items to import
Example of items to import

From here we are able to select/deselect items to import. For this demo we will be selecting the resource group and then pressing w on the keyboard to indicate an import.

3. The following shows the importing/exporting process itself, from a visual perspective it is rather a black box.

Example of the importing processing
Example of the importing processing

4. Once the import/export is complete we are shown the following line which gives you the directory of where the Terraform configuration is generated. By default this will be in the current working directory.

Example of imported state with folder
Example of imported state with folder

Before we move onto the exported Terraform lets look at the other options that aztfexport provides us for exporting/importing resources:

  • resource: allows for exporting/importing a single resource.
  • resource-group: allows for exporting/importing a resource group as well as all resources within that resource group.
  • query: allows exporting/importing a set of resources as defined by an Azure Graph query, you can learn more about those queries at Starter query samples - Azure Resource Graph.
  • mapping-file: allows for exporting/importing resources based on a resource mapping file this is a json file containing the resource ID, type and name

Now we have seen how the export/import process works and what options are available to us as engineers lets look at the resulting generated Terraform. What we get is six files:

  • aztfexportResourceMapping.json: the mapping file between Azure resources and properties that will make up the Terraform resource.
  • aztfexportSkippedResources.txt: resources that will be skipped by the export/import process.
  • import.tf: import blocks for all resources.
  • main.tf: Terraform definitions for all resources within the export/import.
  • provider.tf: provider details.
  • terraform.tf: Terraform constraints.

Next we will look at an excerpt from the interesting files, namely aztfexportResourceMapping.json, main.tf, and import.tf we won't see the full content as its rather lengthy and aztfexport when exporting/importing a Log Analytics Workspace brings all the default queries with it which is rather messy. This first file, the aztfexportResourceMapping.json file contains all resources identified by aztfexport, and defines the Terraform properties required in order to produce resource blocks as well as the resource ID for the import blocks.

{
	"/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf": {
		"resource_id": "/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf",
		"resource_type": "azurerm_resource_group",
		"resource_name": "res-0"
	},
	"/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.App/containerApps/ca-aue-dev-tf": {
		"resource_id": "/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.App/containerApps/ca-aue-dev-tf",
		"resource_type": "azurerm_container_app",
		"resource_name": "res-1"
	},
	"/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.App/managedEnvironments/cae-aue-dev-tf": {
		"resource_id": "/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.App/managedEnvironments/cae-aue-dev-tf",
		"resource_type": "azurerm_container_app_environment",
		"resource_name": "res-2"
	},
	"/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.OperationalInsights/workspaces/workspacergauedevtfb34b": {
		"resource_id": "/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.OperationalInsights/workspaces/workspacergauedevtfb34b",
		"resource_type": "azurerm_log_analytics_workspace",
		"resource_name": "res-3"
	},
	"/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.Storage/storageAccounts/saaurdevtf": {
		"resource_id": "/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.Storage/storageAccounts/saaurdevtf",
		"resource_type": "azurerm_storage_account",
		"resource_name": "res-543"
	}
}

Lets quickly review those properties:

  • resource_id: the Azure Resource ID for the resource, this will act as the ID in Terraform state.
  • resource_type: the fully qualified resource type which will be used in the resource block.
  • resource_name: this will be used as the identifier for the given resource block.
Now we can look into the main.tf which takes the above resource properties and defines the resource blocks. As you can see the resource_type and resource_name are clearly visible. In a production utilisation of this product it would be an idea to either intercept the aztfexportResourceMapping.json file and provide more usable resource_name definitions or utilise a move block most export/import.

resource "azurerm_resource_group" "res-0" {
  location = "australiaeast"
  name     = "rg-aue-dev-tf"
}
resource "azurerm_container_app" "res-1" {
  container_app_environment_id = "/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.App/managedEnvironments/cae-aue-dev-tf"
  name                         = "ca-aue-dev-tf"
  resource_group_name          = "rg-aue-dev-tf"
  revision_mode                = "Single"
  ingress {
    external_enabled = true
    target_port      = 80
    traffic_weight {
      latest_revision = true
      percentage      = 100
    }
  }
  template {
    container {
      cpu    = 0.25
      image  = "mcr.microsoft.com/k8se/quickstart:latest"
      memory = "0.5Gi"
      name   = "simple-hello-world-container"
    }
  }
  depends_on = [
    azurerm_container_app_environment.res-2,
  ]
}
resource "azurerm_container_app_environment" "res-2" {
  location            = "australiaeast"
  name                = "cae-aue-dev-tf"
  resource_group_name = "rg-aue-dev-tf"
}
resource "azurerm_log_analytics_workspace" "res-3" {
  location            = "australiaeast"
  name                = "workspacergauedevtfb34b"
  resource_group_name = "rg-aue-dev-tf"
}
resource "azurerm_storage_account" "res-543" {
  account_replication_type         = "RAGRS"
  account_tier                     = "Standard"
  allow_nested_items_to_be_public  = false
  cross_tenant_replication_enabled = false
  location                         = "australiaeast"
  name                             = "saaurdevtf"
  resource_group_name              = "rg-aue-dev-tf"
}

Now that we have our mappings and the generated Terraform it becomes a simple task to import those into Terraform state, this is done via the import block which is exceptionally powerful as it gives tracking and audibility to importing resources now. This is where the mapping file was used to generate all the relevant attributes for these blocks to work.

import {
  id = "/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf"
  to = azurerm_resource_group.res-0
}
import {
  id = "/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.App/containerApps/ca-aue-dev-tf"
  to = azurerm_container_app.res-1
}
import {
  id = "/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.App/managedEnvironments/cae-aue-dev-tf"
  to = azurerm_container_app_environment.res-2
}
import {
  id = "/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.OperationalInsights/workspaces/workspacergauedevtfb34b"
  to = azurerm_log_analytics_workspace.res-3
}
import {
  id = "/subscriptions/ecf2fa19-c059-4906-933c-9ab85fb327f8/resourceGroups/rg-aue-dev-tf/providers/Microsoft.Storage/storageAccounts/saaurdevtf"
  to = azurerm_storage_account.res-543
}

It is worth noting that this file can be deleted once the import is completed into state.

So that was a look into the aztfexport utility from Microsoft, we learned that it easily enables us as engineers to get pre-existing Azure assets into Terraform. Whilst the resultant Terraform code may not always be the best it is a supremely better starting point than having to manually craft the Terraform code by hand. I can see this being extremely useful to organisations who started their IaC journey after they had already started the journey into the cloud.

Get started using Scalr by signing up today.

Start using the Terraform platform of the future.

A screenshot of the modules page in the Scalr Platform