slide

What's new in the azure rm provider version 4?

Ned Bellavance
9 min read

Cover

Major version 4.x of the AzureRM provider was just released! What’s new, what’s different, and what do you need to know? That’s what I’m going to cover in this post.

Introduction

The AzureRM provider has been on major version 3 for two and a half years! Since that release, a ton of new resources and data sources have been added to the provider and they’ve kept up the pace of releasing a new minor version of the provider roughly every two weeks. As such, the most recent minor version for major version 3 is 116. As in one-hundred and sixteen minor release 🤯. A minor release does not have breaking changes, meaning all significant changes to the provider have been waiting until now to be implemented!

So what are the big, breaking changes? They fall into three categories:

  • Provider property changes
  • Removed resources and data sources
  • Removed or altered properties of resources and data sources

There’s also some new functionality added to the provider which doesn’t break existing code, but enhances the use of the provider. In particular, there are two changes:

  • Provider-defined functions
  • Resource provider registration

Why don’t we start with the new functionality and then get into breaking changes?

New Functionality

Provider-defined Functions

Provider-defined functions were introduced to Terraform in version 1.8. Of the providers adopting this feature, azurerm was notably absent at launch. That’s not because HashiCorp didn’t want to add provider-defined functions to AzureRM, but rather because the versions of the provider SDK being used made it difficult. Major version 4 adopts the Terraform plugin framework for some portions of the provider, which brings with it support for provider-defined functions.

There are two new provider-defined functions: normalise_resource_id and parse_resource_id. As you probably already know, Azure resource IDs are really long strings that uniquely identify a resource. They include the subscription, resource group, resource provider type, parent resource, etc. While you could try and manipulate with these strings using the built-in functions of Terraform, the two new provider-defined functions make it a bit easier.

The normalise_resource_id function takes a resource ID and attempts to normalize the capitalization of its elements to match what the Azure API is expecting. The Azure API is case sensitive (sometimes) and if you’re constructing a resource ID by hand or receiving it from another source- like an input variable- you may want to normalize it before using it.

variable "vnet_resource_id" {
    type        = string
    description = "Resource ID of VNet"
}

locals {
    vnet_resource_id = provider::azurerm:normalise_resource_id(var.vnet_resource_id)
}

The above code would create a normalized version of a VNet resource ID for use in the rest of your code. This is also super helpful for import blocks, where you have to supply a resource ID to successfully import your resource:

import {
    to = azurerm_container_app.example
    id = provider::azurerm::normalise_resource_id(var.container_app_id)
}

The parse_resource_id function breaks a resource ID into its component parts. The output is a map with the following keys:

  • full_resource_type
  • parent_resources
  • resource_group_name
  • resource_name
  • resource_provider
  • resource_scope
  • resource_type
  • subscription_id

Depending on the resource type, some of the values may be set to an empty value. For instance, an azurerm_virtual_network would have the following fields set:

parsed_id_vnet = {
  "full_resource_type" = "Microsoft.Network/virtualNetworks"
  "parent_resources" = tomap({})
  "resource_group_name" = "function-example-rg"
  "resource_name" = "function-example-vnet"
  "resource_provider" = "Microsoft.Network"
  "resource_scope" = ""
  "resource_type" = "virtualNetworks"
  "subscription_id" = "XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX"
}

Since a VNet doesn’t have parent resources, the parent_resources field is empty. This function is very similar in nature to the arn_parse function in the aws provider.

Resource Provider Registrations

The Azure Resource Manager control plane is built on the idea of Resource Providers (RP) which are responsible for the lifecycle of resources under a certain category. For instance, the VNet shown above is of resource type Microsoft.Network/virtualNetworks, which means it uses the Microsoft.Network Resource Provider.

When you create a subscription in Azure, a subset of Resource Providers are registered for use by default. You can find the full list here, just look for any namespace that has “registered by default” appended to it. Most of the default services are core to the platform, like microsoft.support or Microsoft.Billing. Bonus points for inconsistent capitalization! Good thing there’s a function to help with that 😂.

All other Resource Providers need to be registered before you can create or manage resources that leverage the RP. When you deploy a resource using the Portal or an ARM template, Azure checks to see if the necessary RP is registered. If it isn’t, Azure Resource Manager registers the RP for you (assuming you have permissions to do so.)

To avoid potential deployment errors, the default behavior of the azurerm provider is to register every single RP that isn’t already registered. That’s… unnecessary and actually goes against Microsoft’s guidance:

“Register a resource provider only when you’re ready to use it. This registration step helps maintain least privileges within your subscription. A malicious user can’t use unregistered resource providers.”

Not only that, but if the account you’re using doesn’t have permissions to register RPs, you’ll get an error back from Terraform. The workaround for previous versions of the azurerm provider was the argument skip_provider_registration. When set to true, Terraform wouldn’t try to register any RPs, which could result in a deployment error.

Clearly this was a very blunt solution, and version 4 of the azurerm provider takes a slightly more nuanced approach. There are two new arguments for the provider: resource_provider_registrations and resource_providers_to_register.

The resource_provider_registrations argument can be set to a pre-defined group of RPs to automatically register. Currently the allowed values are core, extended, all, none, and legacy. I would imagine more may be added over time. The current list of RPs included in each group can be found on GitHub.

The resource_providers_to_register argument takes a list of Resource Providers to register. This can be used in tandem with resource_provider_registrations to register additional RPs that are not part of the grouping. If you want to be REALLY specific, you could set resource_provider_registrations to none and only list the RPs your want registered with resource_providers_to_register. Is it worth doing that? That’s for you to decide.

With the introduction of the two new arguments, the skip_provider_registration argument has been removed. If you want the same functionality, simply set resource_provider_registrations to none.

This is a breaking change if you were using the skip_provider_registration before. Speaking of breaking changes…

Breaking Changes

Subscription ID Required

Possibly the biggest breaking change is making Subscription ID a required argument in the provider. For anyone who uses Azure CLI based authentication for their configurations, you’ll now get an error when you attempt to run terraform plan or terraform apply:

Planning failed. Terraform encountered an error while generating this plan.

│ Error: `subscription_id` is a required provider property when performing a plan/apply operation 

When using the Azure CLI for authentication, the default behavior was to use the credentials and currently selected subscription from the CLI for Terraform deployments. If you wanted to use a different subscription, you could run az account set -s SUBSCRIPTION_NAME and Terraform would use that one.

Did this lead to accidental deployments in the wrong subscription? Almost certainly. The implicit relationship between the Azure CLI and the provider made it all too easy to run terraform apply on the wrong subscription and inadvertently take down Prod when you though the Dev subscription was selected.

Version 4 makes the relationship explicit by requiring that you set the target subscription either through the subscription_id argument in the provider block or through the ARM_SUBSCRIPTION_ID environment variable. This only impacts Azure CLI based authentication, as every other authentication type requires you to set the subscription ID explicitly.

I can hear the clatter of a thousand Terraform tutorials all being updated at once, and I probably have to go back and make sure none of my example code is broken by this change. This is why its so important to pin the version of your provider to a at least a major version if not more specific.

Resources and Data Sources

I’m not going to list out every single resources or data source that has been removed or altered in a breaking way. The 4.0 Upgrade Guide does an excellent job of that. But I do want to point out some significant changes that I think are worth mentioning.

Azure Kubernetes Cluster

There’s two big things to note with AKS. First, the azurerm_kubernetes_cluster resource will only use the stable version of the AKS API. There are two versions of the API: stable and preview. Previously, the AKS cluster resource used a mix of the two APIs to allow cutting-edge and experimental features to be available to the resource. The AKS team politely asked Microsoft and HashiCorp to knock it off and only use the stable API. You know, because it’s the stable one. This means that certain arguments will no longer be available in version 4.0 of the provider.

If you wish to keep using the preview AKS API, you can deploy your AKS cluster using the azapi provider. That gives you full access to any Azure APIs exposed to the public, including experimental and preview versions.

The other big change is a bunch of removed and changed arguments in the azurerm_kubernetes_cluster resources. There are 23 changes in total including removals, renaming, and accepted values. If you’re managing an AKS cluster with Terraform, you’re going to want to pay special attention to the provider changes!

Database Services

There are a ton of database related resources that are being replaced. Basically, all resources that start with azurerm_sql are being replaced with resources that start with azurerm_mssql, and all the resources starting with azurerm_mariadb or azurerm_mysql are being replaced with azurerm_mysql_flexible resources. If you happen to be using these older resources, some have been superseded and others are being retired entirely.

List to Set

The azurerm_subnet and azurerm_virtual_network resources have arguments that are changing the accepted value type from a list to a set. As a quick reminder, a set in Terraform is an unordered collection of unique elements. Since it is unordered, you cannot refer to an individual element using an index, e.g. local.my_list[0]. Instead, you’ll be using a for expression to loop through the contents of the set. Also, when passing a list to these arguments, you’ll need to use the toset() function to convert it to a set.

These changes may require rewriting some of your existing code to handle the difference between a set and list.

Conclusion

It has been two and a half years since the last major version of azurerm! 30 MONTHS! I’m pretty sure in that time the aws provider had two major version releases. Clearly there was a lot of work and thought put into this newer version of the provider, with an eye towards managing change going forward.

I’m really excited to see the AzureRM provider use the newer plugin framework, and I think it will simplify the maintenance of the provider in the future. With any luck that means quicker additions of new resources and properties when they’re released by Microsoft.