Skip to main content

Export Terraform Code from the Azure Portal

·1031 words·5 mins
Azure Terraform

A nice feature that Microsoft Azure has had for as long as I have been using Azure is the ability to export resources as code from the Azure portal.

For a long time you could only export resources as ARM templates. If you were working with ARM templates this was great, but if you were using Azure Bicep you needed to first convert the exported template into Bicep. Converting from ARM templates to Bicep templates was easy. The VS Code provider even supported the paste as Bicep feature for ARM code.

A while ago the Azure portal started supporting Bicep as an alternative for exported resources directly in the portal. This saves you the hassle of translating templates.

But if you were using HashiCorp Terraform you still needed to do some hands-on translation from ARM or Bicep to Terraform.

A few months ago this changed when a (hidden?) preview feature for exporting as Terraform appeared. This feature is now in public preview!

How to export resources as Terraform code from the Azure portal
#

There is a new resource provider that you need to enable for your Azure subscription unless it is already enabled.

Go to your Azure subscription and click on Resource Providers, then search for and select Microsoft.AzureTerraform and click on Register.

Enable the Microsoft.AzureTerraform provider for your Azure subscription

Now you will be able to export resources as Terraform.

To test this, go to a resource. Click on Export template in the menu on the left. Select the Terraform tab next to ARM template and Bicep. Select either the AzureRM or AzAPI provider and copy the code from the code window. In the example image below a storage account is exported.

Export an Azure storage account as Terraform code

For the example above the exported code looks like this:

terraform {
  required_providers {
    azurerm = {
      source  = "azurerm"
      version = "4.5.0"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azurerm_storage_account" "res-0" {
  access_tier                       = "Hot"
  account_kind                      = "StorageV2"
  account_replication_type          = "LRS"
  account_tier                      = "Standard"
  allow_nested_items_to_be_public   = false
  allowed_copy_scope                = ""
  cross_tenant_replication_enabled  = false
  default_to_oauth_authentication   = false
  dns_endpoint_type                 = "Standard"
  edge_zone                         = ""
  https_traffic_only_enabled        = true
  infrastructure_encryption_enabled = false
  is_hns_enabled                    = false
  large_file_share_enabled          = true
  local_user_enabled                = true
  location                          = "swedencentral"
  min_tls_version                   = "TLS1_2"
  name                              = "tfvalidation"
  nfsv3_enabled                     = false
  primary_access_key                = "" # Masked sensitive attribute
  primary_blob_connection_string    = "" # Masked sensitive attribute
  primary_connection_string         = "" # Masked sensitive attribute
  public_network_access_enabled     = true
  queue_encryption_key_type         = "Service"
  resource_group_name               = "rg-storage"
  secondary_access_key              = "" # Masked sensitive attribute
  secondary_blob_connection_string  = "" # Masked sensitive attribute
  secondary_connection_string       = "" # Masked sensitive attribute
  sftp_enabled                      = false
  shared_access_key_enabled         = true
  table_encryption_key_type         = "Service"
  tags = {
    gdpr = "true"
  }
  blob_properties {
    change_feed_enabled           = false
    change_feed_retention_in_days = 0
    default_service_version       = ""
    last_access_time_enabled      = false
    versioning_enabled            = false
    container_delete_retention_policy {
      days = 7
    }
    delete_retention_policy {
      days                     = 7
      permanent_delete_enabled = false
    }
  }
  share_properties {
    retention_policy {
      days = 7
    }
  }
}

We see that we get all the code necessary to provision this storage account using Terraform. I am not a fan of the naming of the resource, res-0 (avoid using dashes in the name!). However, the name is easy to correct but still a bit tedious if you are exporting a large number of resources.

Note that to actually start managing this resource with Terraform we must also include an import block similar to the following:

import {
  to = azurerm_storage_account.res-0
  id = "<azure storage account id>"
}

If you are planning to first delete the resource from Azure and then provision it using Terraform, then you do not need to add the import block.

What about dependencies? Can the export handle how resources depend on each other? To investigate this I tried to export the resource group where my storage account is located. The exported template looks like this:

terraform {
  required_providers {
    azurerm = {
      source  = "azurerm"
      version = "4.5.0"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "res-0" {
  location   = "swedencentral"
  managed_by = ""
  name       = "rg-storage"
  tags = {
    gdpr = "true"
  }
}

resource "azurerm_storage_account" "res-1" {
  access_tier                       = "Hot"
  account_kind                      = "StorageV2"
  account_replication_type          = "LRS"
  account_tier                      = "Standard"
  allow_nested_items_to_be_public   = false
  allowed_copy_scope                = ""
  cross_tenant_replication_enabled  = false
  default_to_oauth_authentication   = false
  dns_endpoint_type                 = "Standard"
  edge_zone                         = ""
  https_traffic_only_enabled        = true
  infrastructure_encryption_enabled = false
  is_hns_enabled                    = false
  large_file_share_enabled          = true
  local_user_enabled                = true
  location                          = "swedencentral"
  min_tls_version                   = "TLS1_2"
  name                              = "tfvalidation"
  nfsv3_enabled                     = false
  primary_access_key                = "" # Masked sensitive attribute
  primary_blob_connection_string    = "" # Masked sensitive attribute
  primary_connection_string         = "" # Masked sensitive attribute
  public_network_access_enabled     = true
  queue_encryption_key_type         = "Service"
  resource_group_name               = "rg-storage"
  secondary_access_key              = "" # Masked sensitive attribute
  secondary_blob_connection_string  = "" # Masked sensitive attribute
  secondary_connection_string       = "" # Masked sensitive attribute
  sftp_enabled                      = false
  shared_access_key_enabled         = true
  table_encryption_key_type         = "Service"
  tags = {
    gdpr = "true"
  }
  blob_properties {
    change_feed_enabled           = false
    change_feed_retention_in_days = 0
    default_service_version       = ""
    last_access_time_enabled      = false
    versioning_enabled            = false
    container_delete_retention_policy {
      days = 7
    }
    delete_retention_policy {
      days                     = 7
      permanent_delete_enabled = false
    }
  }
  share_properties {
    retention_policy {
      days = 7
    }
  }
  depends_on = [
    azurerm_resource_group.res-0,
  ]
}

The exported storage account has an explicit dependency on the resource group through the depends_on meta-argument. However, if we built this template ourselves we would have used an implicit dependency similar to this:

resource "azurerm_resource_group" "default" {
  name       = "rg-storage"
  location   = "swedencentral"
}

resource "azurerm_storage_account" "default" {
  resource_group_name = azurerm_resource_group.default.name
  # ...
}

Still, most of these implicit dependencies are easy to add manually after you have exported the template.

Exporting templates like this should probably not be a routine part of your work, it should only be used in special situations. For these situations it is fine to do some manual work to improve the exported template.


Note that I could also have exported resources with the AzAPI provider instead of the normal AzureRM provider. This might be required in some cases if the Azure portal can’t handle the export using the AzureRM provider.

Summary
#

You can now export resources as Terraform code (HCL) from the Azure portal. This is a great way to convert something you have created in the Azure portal to proper infrastructure as code with Terraform.

Mattias Fjellström
Author
Mattias Fjellström
Cloud architect · Author · HashiCorp Ambassador