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.
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.
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.
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.
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 nameNow 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.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.