One of the many benefits of Azure Resource Manager operations is the fact they’re idempotent. Regardless of whether you’re using ARM templates or the intermediate language Bicep, resource management operations will result in the same state of your resources time after time you deploy them. Although this behaviour is desirable most of the time, there are always exceptions to the rule. There are some cases where you may want to desire a different outcome depending on current state. To prove this case, I’ll give you a few examples.
Problem statement 1: creating an App Service / Function App
Creating an App Service or Function App instance via ARM or Bicep templates is easy. But what happens if you (accidentally) deploy your template again after an actual application has been deployed into your App Service? This could lead to undesirable behaviour, like accidentally wiping your App Configuration settings and thereby taking your whole application offline as a result. In this case you don’t want your template to be idempotent, in fact, you don’t want to execute it at all depending on the current state (does the resource already exist?).
Problem statement 2: Updating your API Management instance after initial creation
Updating an Azure API Management instance after initial creation will ‘lock’ your API Management against any updates. The idempotency of your ARM templates does not form any issue, but the fact your API Management instance is temporarily unavailable for your API developers might cause some upset developers and lead to unnecessary loss of productivity. This is another example where you may want to skip the ARM template deployment entirely depending on the current state.
Problem statement 3: creating an API Management instance referencing a Key Vault
When creating an Azure API Management instance, it’s possible to reference Key Vault certificates for the purpose of TLS encryption on your own custom domains. In order to authorise access to the Key Vault assets, you apply RBAC on the Key Vault and grant access to API Management’s managed identity. But how can you provide access to the identity when the API Management instance isn’t created yet? This ‘chicken and egg’ problem arises in many situations where a system-assigned managed identity is used in combination with a Key Vault. A solution to this problem would be to perform two ARM template deployments, where the first deployment creates/updates the instance without certificates, and the second deployment references the Key Vault certificates. The downside to this is that it will lead to very lengthy deployment times when you run your templates for the second time, which is common in IaC CI/CD scenarios.
Although not so much related to idempotency, in this case you want to be able to check API Management exists prior to any Azure resource deployments.
Solution: checking if a resource exists
What I’ve tried to highlight above is that at times you want your deployments to behave differently depending on the current state of your Azure resources. Although ARM and Bicep templates support conditional deployments, these conditions can only be applied to static data known prior to template deployment. ARM does not have the ability to check if a resource exists, despite some
A solution is to ‘wire’ multiple deployment steps together in scripts or deployment pipelines, and use Azure CLI or other means to check whether resources exist. My biggest objection against baking in all this logic into pipelines is portability. Instead of a simple, single deployment template that can be deployed from anywhere, you’re now composing a solution that consists of several dependent components and needs to be ‘wired up’ by a language (e.g. PowerShell, bash) or tooling (e.g. Azure CLI) that is heavily dependent on its runtime host. In the past, I’ve built solutions using Azure DevOps pipelines using bash scripts and Azure CLI, but I couldn’t run these pipelines from my local machine nor were they easily portable to other runners.
The magic glue: deployment scripts
I won’t go into great detail on deployment scripts in this blog (more info here), but in essence they allow you to perform Azure PowerShell or Azure CLI scripts within an ARM resource deployment. We can use the output of these scripts within the ARM template itself. In other words, we could perform an Azure CLI command to check if a resource exists, and use the output result as a conditional on a subsequent resource deployment within the same template.
Deployment scripts run in Azure container instances; its lifecycle is fully managed by Azure so there’s no additional infrastructure required to be set up. When executing Azure CLI or PowerShell commands, your script will have to authenticate itself to Azure again since deployment scripts don’t run under the identity of the execution host (they’re running remotely in ACI…). Currently only user-assigned managed identities are supported. Depending on the execution logic within your scripts, you’ll need to create a role assignment (e.g. Reader to check if a resource exists) for this identity in your target subscription or resource group.
I’ll present you with two different options for the ‘check if resource exists’ solutions.
Solution 1: Bicep template requiring user-assigned managed identity
The script below requires you to provide the resource ID of the user-assigned managed identity which has sufficient (Reader) permissions in the resource group to check for resource existence. The script returns a boolean value indicating if the resource exists, or not. Given the script creates a deployment script in the resource group, you’ll require contributor permissions in order to deploy the Bicep module.
Using the module in your Bicep template is demonstrated in below parent and dependent template snippets:
In order to test your Bicep template simply perform a resource deployment on your resource group, e.g.
az deployment group create –resource-group myresourcegroup –template-file test.bicep
Solution 2: Bicep template with automatic user-assigned managed identity lifecycle management
The script below operates similarly to the script above, with the exception of not having to provide an identity. The identity of the execution host requires not only contributor permissions but also the ‘User Access Administrator’ or ‘Owner’ role in order to create a role assignment as part of the Bicep template deployment.
The script created a managed identity and pauses for a minute in order for the user-assigned managed identity becomes available for role assignment.
Code can be found here
The module can be incorporated into your Bicep template in the same way as the script outlined in ‘Solution 1’.
If you have no issue with having a user-assigned managed identity floating around in your Azure subscription I tend to opt for solution 1. Not only does the script run faster than solution 2, but it proved to be more reliable than solution 2. The lifecycle management of the user-assigned managed identity in solution 2 can be less robust. If your script fails the role assignments will not be properly cleaned up. This leads to execution errors upon the next time you’ll deploy the template.
I hope the solutions above provide you with a workable solution whilst a native ARM/Bicep solution isn’t around.