3 Annoying Azure ARM Template Gotchas (And How To Fix Them)

The most popular method of managing Azure resources in a programmatic fashion is Azure Resource Management Templates – or ARM templates for short. Much like Terraform, it’s a desired state type tool that you can define what you need, but Azure will work out the actual details of how to make it so (For the most part anyway!).

Over the years, I’ve ran into a few gotchas with these templates that I seem to forget and run into time and time again. Things that on the surface should be simple, but actually are confusing as hell. Often I end up googling the same issue every 3 months when I run into it again. So rather than do a post for each of these, I thought, why not combine them all together in a somewhat cheatsheet. If I’m having to constantly look these up, maybe you are too!

For now, I’ve named this “3 annoying gotchas”, but I’m likely to come back and edit this so maybe by the time you read this, we will be a little higher!

Let’s get started!

You Need To “Concat” A Database Connection String

In my ARM templates, I typically spin up an Azure SQL Database and a Keyvault instance. I make the Keyvault instance rely on the SQL Database, and immediately take the connection string and push it into keyvault. I do this so that there is never a human interaction that sees the connection string, it’s just used inside the ARM template, and straight into Keyvault.

But there’s an annoying gotcha of course! How do you get the connection string of an Azure SQL database in an ARM Template? You can’t! (Really, you can’t!). Instead you need to use string concatenation to build your connection string for storage.

As an example (And note, this is heavily edited, but should give you some idea) :

{
    "parameters" : {
        "sqlPassword" : {
            "type" : "securestring"
        }
    }, 
    ....
    "variables": {
        "sqlServerName": "MySQLServerName", 
        "sqlDbName" : "MySqlDatabase"
    }, 
    ....
    {
      "type": "Microsoft.KeyVault/vaults/secrets",
      "name": "MyVault/SQLConnectionString",
      "apiVersion": "2018-02-14",
      "location": "[resourceGroup().location]",
      "properties": {
        "value": "[concat('Server=tcp:',reference(variables('sqlserverName')).fullyQualifiedDomainName,',1433;Initial Catalog=',variables('sqlDbName'),';Persist Security Info=False;User ID=',reference(variables('sqlserverName')).administratorLogin,';Password=',parameters('sqlPassword'),';Connection Timeout=30;')]"
      }
    },
}

Or if we pull out just the part that is creating our SQL Connection String :

[concat('Server=tcp:',reference(variables('sqlserverName')).fullyQualifiedDomainName,',1433;Initial Catalog=',variables('sqlDbName'),';Persist Security Info=False;User ID=',reference(variables('sqlserverName')).administratorLogin,';Password=',parameters('sqlPassword'),';Connection Timeout=30;')]

So why do we have to go to all of this hassle just to get a connection string? There’s actually two reasons :

  • A connection string may have additional configuration, such as a timeout value. So it’s usually better that you get the connection string exactly how you need it.
  • But the most important reason is that a SQL Password, when set in Azure, is a blackbox. There is no retrieving it. You can only reset it. So from the ARM Templates point of view, it can’t ask for the connection string of a SQL database because it would never be able to get the password.

On that last note, it’s why when you try and grab your connection string from the Azure portal, it comes with a {your_password} field where your password will be.

Connecting Web Apps/Functions To Application Insights Only Requires The Instrumentation Key

I talked about this a little in a previous post around connecting Azure Functions to App Insights. I think it could be a hold over from the early days of App Insights when there wasn’t as much magic going on, and you really did have to do a bit of work to wire up Web Applications to App Insights. However now, it’s as simple as adding the Instrumentation Key as an app setting and calling it a day.

For example :

{
  "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
  "value": "[reference(resourceId('Microsoft.Insights/components', variables('AppInsightsName')), '2014-04-01').InstrumentationKey]"
}

Also notice in this case, we can get the entire instrumentation key via the ARM template. I want to point this out because I’ve seen people manually create the Application Insights instance, then loop back around and run the ARM template with the key as an input parameter. You don’t have to do this! You can grab it right there in the template.

And again, as long as you use the appsetting name of “APPINSIGHT_INSTRUMENTATIONKEY” on either your Web Application or Azure Function, you are good to go!

Parameters File Cannot Contain Template Expressions

There are many times where you read a tutorial that uses a parameters file with a keyvault reference.

As an example, consider the following parameters file :

"parameters": {
    "serviceBusName": {
        "reference": {
            "keyVault": {
                "id": "/subscriptions/GUID/resourceGroups/KeyVaultRG/providers/Microsoft.KeyVault/vaults/KeyVault"
            },
        "secretName": "serviceBusName"
        }
    }
}

The idea behind this is that for the parameter of serviceBusName, we should go to keyvault to find that value. However, there’s something very wrong with this. We have a hardcoded subscription and resource group name. It makes far more sense for these to be dynamic, because between Dev, Test and Prod, we may have different subscriptions and/or resource groups right?

So, you may think this could be solved like so :

"parameters": {
    "serviceBusName": {
        "reference": {
            "keyVault": {
                "id": "[resourceId(subscription().subscriptionId, resourcegroup().name, 'Microsoft.KeyVault/vaults', parameters('KeyVaultName'))])"
            },
        "secretName": "serviceBusName"
        }
    }
}

But unfortunately :

resourceId function cannot be used while referencing parameters

You cannot use the resourceId function, or really any template expressions (Not even concat), inside a parameters file. It’s static text only. What that means is, frankly, that references to keyvault from a parameters file is pointless. In no situation have I ever wanted a hardcoded subscription ID in an ARM template, it just wouldn’t happen.

Microsoft’s solution for this is to push for the use of nested templates. In my personal view, this adds a tonne of complexity, but it’s an option. What I generally end up doing is trying to avoid Keyvault secrets at all. Usually my C# application is talking to keyvault anyway so there is no need for additional parameters like the above.

In anycase, the actual point of this section is to say that a parameters file cannot be dynamic without using nested templates. Whether that be for keyvault references or something else, you’ll have to find a way around using dynamic parameters.

Leave a Comment