Azure Biceps is a Domain-specific language for deploying Azure resources declaratively. Azure Biceps makes it easy to manage resources using code. However, a significant limitation of Azure Biceps is the inability to check if a resource exists before deploying it.
This limitation can lead to errors, such as attempting to create a resource that already exists, which can cause unnecessary downtime and increased costs. In this blog post, we will discuss a solution to this limitation using deploymentScripts to run code remotely.
The Lack of Capability to Check if Resource Exists in Azure Biceps
The inability to check if a resource exists before deploying it can cause significant issues when deploying Azure resources. The lack of this capability in Azure Biceps has been a long-standing issue that has been tracked on the Azure Biceps GitHub repository.
To address this issue, we need to find a way to check if a resource exists before deploying it. One solution to this issue is to use Azure deploymentScripts to run code remotely.
This problem is being tracked in this issue for years, go there to find the latest updates and solutions, you don’t know, they might have already solved it.
https://github.com/Azure/bicep/issues/4023
Using deploymentScripts to Run Code Remotely
DeploymentScripts allow you to run code remotely on Azure resources. Using deploymentScripts, you can run code to check if a resource exists before attempting to deploy it.
The solution involves creating a deploymentScript that runs an ‘az resource list’ command to determine whether a resource exists. The deploymentScript takes in the resource name, client ID, and client secret as parameters. If the resource exists, the deploymentScript outputs a Boolean value of true; otherwise, it outputs false.
targetScope = 'resourceGroup'
@description('Resource name to check in current scope (resource group)')
param resourceName string
@description('Client Id that is going to be used to log into az-cli')
@secure()
param identityClientId string
@description('Client Secret that is going to be used to log into az-cli')
@secure()
param identityClientSecret string
param location string = resourceGroup().location
param utcValue string = utcNow()
// used to pass into deployment script
var resourceGroupName = resourceGroup().name
// The script below performs an 'az resource list' command to determine whether a resource exists
resource resource_exists_script 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
name: 'resource_exists'
location: location
kind: 'AzureCLI'
properties: {
forceUpdateTag: utcValue
azCliVersion: '2.34.1'
timeout: 'PT10M'
environmentVariables: [
{
name: 'AZURE_CLIENT_ID'
secureValue: identityClientId
}
{
name: 'AZURE_CLIENT_SECRET'
secureValue: identityClientSecret
}
{
name: 'AZURE_TENANT_ID'
secureValue: tenant().tenantId
}
{
name: 'RESOURCE_GROUP'
value: resourceGroupName
}
{
name: 'RESOURCE_NAME'
value: resourceName
}
]
scriptContent: '''
az login --service-principal -u $AZURE_CLIENT_ID -p $AZURE_CLIENT_SECRET --tenant $AZURE_TENANT_ID
result=$(az resource list --resource-group $RESOURCE_GROUP --name $RESOURCE_NAME)
echo $result
echo $result | jq -c '{Result: map({name: .name})}' > $AZ_SCRIPTS_OUTPUT_PATH
'''
cleanupPreference: 'OnSuccess'
retentionInterval: 'P1D'
}
}
output exists bool = length(resource_exists_script.properties.outputs.Result) > 0
This solution can be applied to various Azure resources deployment that have prolems with reiterated deployments, such as KeyVault access policies or Function app settings. This is an example of using it as part of a Keyvault deployment:
@description('Location of the KeyVault resource to create')
param location string = resourceGroup().location
@description('Name of the KeyVault resource to create')
param keyVaultName string
@description('Client Id that is going to be used to log into az-cli')
@secure()
param identityClientId string
@description('Client Secret that is going to be used to log into az-cli')
@secure()
param identityClientSecret string
// Check whether the keyvault exists or not
module existsKv './resourceExistsSPN.bicep' = {
name: 'exists_kv'
params: {
identityClientId: identityClientId
identityClientSecret: identityClientSecret
resourceName: keyVaultName
location: location
}
}
// Get current access policies if they exists in the Keyvault if it exists
module accessPolicies 'accessPolicyCurrent.bicep' = {
name: 'accessPolicy-${keyVaultName}'
params: {
keyVaultName: keyVaultName
existingKeyVault: existsKv.outputs.exists
}
}
Usage as part of a Devops pipeline
Here is an example of how this module can be deployed. It makes use of the identity of the service connection by setting addSpnToEnvironment: true
and using the variables $servicePrincipalId
, $servicePrincipalId
variables:
azureSubscription: 'serviceConnection'
environment: 'environment'
bicepFile: 'deployKeyVault.bicep'
workingDirectory: ''
resourceGroup: 'resourcegroup'
deploymentParameters: >-
location=northeurope
keyVaultName=somekeyvault
identityClientId=$servicePrincipalId
identityClientSecret=$servicePrincipalKey
pool:
vmImage: ubuntu-latest
stages:
- stage: Deploy
condition: succeeded()
jobs:
- deployment: Deploy
environment: $
strategy:
runOnce:
deploy:
steps:
- checkout: self
- task: AzureCLI@2
displayName: 'Deploy bicep template'
inputs:
workingDirectory: $
azureSubscription: '$'
addSpnToEnvironment: true
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az deployment group create \
--template-file $ \
--resource-group $ \
--parameters $
Conclusion
Azure Bicep is a very powerful domain-specific language that enables declarative deployment of Azure resources. However, it has some significant limitations that need to be addressed. One of the most significant limitations is the inability to check if a resource exists before deploying it. This can lead to problems such as attempting to create a resource that already exists, which can cause unnecessary downtime and increased costs. This limitation can also lead to issues with KeyVault access policies and Function app settings, which can be overwritten if not checked before deployment.
Another limitation of Azure Bicep is the lack of support for the what-if command. This command allows you to preview the changes that will be made before deploying them, making it easier to catch potential errors before they occur.
Overall, while Azure Bicep is an incredibly useful tool, it is important to be aware of its limitations and to take steps to work around them when necessary. This post is just one of the workarounds we have used, already there are better solutions to the problem, as seen by this answer to the above issue: