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