Arm Template conditional output parameters - azure

I have an ARM template which conditionally creates a resource:
{
"type": "Microsoft.Storage/storageAccounts",
"sku": {
"name": "Standard_GRS",
"tier": "Standard"
},
"kind": "BlobStorage",
"name": "[variables('storageAccounts_name')]",
"condition": "[equals(parameters('is_Not_Development'), 'True')]",
"apiVersion": "2017-06-01",
"location": "[resourceGroup().location]",
"scale": null,
"properties": {
"accessTier": "Hot"
},
"dependsOn": []
},
In my output parameters I have the following which causes an error if the resource is not created:
"storageAccountConnectionString": {
"type": "string",
"value": "[Concat('DefaultEndpointsProtocol=https;AccountName=',variables('StorageAccounts_name'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccounts_name')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]"
},
I have tried this:
"storageAccountConnectionString": {
"type": "string",
"condition": "[equals(parameters('is_Not_Development'), 'True')]",
"value": "[Concat('DefaultEndpointsProtocol=https;AccountName=',variables('StorageAccounts_name'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccounts_name')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]"
},
with the condition clause but this is not recognised. How can I make the output parameter conditional?
UPDATE:
I have tried the following:
"storageAccountConnectionString": {
"type": "string",
"value": "[if(equals(parameters('is_Not_Development'),'False'),'null',Concat('DefaultEndpointsProtocol=https;AccountName=',variables('StorageAccounts_name'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccounts_name')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value))]"
},
but it gives me the same error message, it must be evaluating both true and false conditions.

There is a trick to solve this issue and we use it successfully.
Let's see for example how the following template returns a value only if the corresponding resource has been deployed.
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appInsightsLocation": {
"type": "string",
"defaultValue": "",
"allowedValues": [
"",
"northeurope",
"westeurope"
]
}
},
"variables": {
"appInsightsName": "exampleAppInsights",
"planName": "example-plan",
"appInsightsEnabled": "[if(greater(length(parameters('appInsightsLocation')), 0), 'true', 'false')]",
"appInsightsOrPlanResource": "[if(bool(variables('appInsightsEnabled')), concat('Microsoft.Insights/components/', variables('appInsightsName')), concat('Microsoft.Web/serverFarms/', variables('planName')))]",
"appInsightsKeyOrPlanName": "[if(bool(variables('appInsightsEnabled')), 'InstrumentationKey', 'name')]"
},
"resources": [
{
"comments": "The example service plan",
"apiVersion": "2015-08-01",
"type": "Microsoft.Web/serverfarms",
"location": "[resourceGroup().location]",
"name": "[variables('planName')]",
"sku": {
"name": "B1",
"capacity": 1
},
"properties": {
"numberOfWorkers": 1,
"name": "[variables('planName')]"
}
},
{
"comments": "The application insights instance",
"apiVersion": "2014-04-01",
"condition": "[bool(variables('appInsightsEnabled'))]",
"type": "Microsoft.Insights/components",
"location": "[parameters('appInsightsLocation')]",
"name": "[variables('appInsightsName')]",
"properties": {}
}
],
"outputs": {
"appInsightsKey": {
"value": "[if(bool(variables('appInsightsEnabled')), reference(variables('appInsightsOrPlanResource'))[variables('appInsightsKeyOrPlanName')], '')]",
"type": "string"
}
}
The template declares two resources. One app service plan and one Application Insights instance. The AppInsights instance is deployed only if the location parameter is not empty string. So the instrumentation key of this instance is also returned only if it has been created.
To achieve this we also need a resource that is always present. In our case this is the service plan. We use this resource to get the reference when AppInsights is not deployed. This could be any azure resource of course.
The trick happens on the two variables appInsightsOrPlanResource and appInsightsKeyOrPlanName we declare. When appInsightsLocation is provided then those two variables end up referencing the key of the instance which is returned from the output.
When appInsightsLocation is not provided on the other hand those two variables contain a valid reference to the service plan that is not used but it's valid. We need to do this one because if function evaluates always both sides. An empty string is returned from the output in this case though.

I know this is an old question, but in case anyone arrives here, it looks like MSFT has fixed this in two ways now.
In Feb 2019 they fixed the 'if' evaluation to only evaluate the true side.
https://feedback.azure.com/forums/281804-azure-resource-manager/suggestions/31538470-arm-template-if-function-should-not-evaluate-both
In August 2019 they added support for condition: in the outputs.
https://feedback.azure.com/forums/281804-azure-resource-manager/suggestions/19492006-conditional-output-from-arm-template
https://learn.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authoring-templates#outputs
Looks like as long as you're at Azure CLI version 2.0.72 you'll have access to these changes. I just tested both on 2.0.76 and they appear to work.

Related

Use of ARM template function "reference" in "dependsOn" fails with error: The template function 'reference' is not expected at this location

I am using output from a linked template in my ARM template for deployment below are my templates :
Link template :
"resources": [
{
"name": "[variables('clusterName')]",
"type": "Microsoft.Kusto/clusters",
"sku": {
"name": "Standard_D13_v2",
"tier": "Standard",
"capacity": 2
},
"apiVersion": "2020-09-18",
"location": "[parameters('location')]",
"properties": {
"trustedExternalTenants": [],
"optimizedAutoscale": {
"version": 1,
"isEnabled": true,
"minimum": 2,
"maximum": 10
},
"enableDiskEncryption": false,
"enableStreamingIngest": true,
"enablePurge": false,
"enableDoubleEncryption": false,
"engineType": "V3"
}
}
],
"outputs": {
"clusterNameResult": {
"type": "string",
"value": "[variables('clusterName')]"
}
}
Template using this linked template:
"resources": [
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2021-04-01",
"name": "linkedTemplate",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[concat(uri(deployment().properties.templateLink.uri, 'Dataexplorer_Deployment_Template.json'))]",
"contentVersion": "1.0.0.0"
}
},
"copy": {
"name": "databasecopy",
"count": "[length(parameters('databaseNameList'))]"
}
},
{
"type": "Microsoft.Kusto/Clusters/Databases",
"apiVersion": "2020-09-18",
"name": "[variables('databaseNameList').databaseNames[copyIndex()]]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Kusto/Clusters', reference('linkedTemplate').outputs['clusterNameResult'].value)]"
],
"kind": "ReadWrite",
"properties": {
"softDeletePeriod": "P5D",
"hotCachePeriod": "P1D"
},
"copy": {
"name": "databasecopy",
"count": "[length(parameters('databaseNameList'))]"
}
},
{
"type": "Microsoft.Kusto/Clusters/Databases/PrincipalAssignments",
"apiVersion": "2020-09-18",
"name": "[variables('databaseNameList').databaseNames[copyIndex()]]",
"dependsOn": [
"[resourceId('Microsoft.Kusto/Clusters/Databases', variables('databaseNameList').databaseNames[copyIndex()])]",
"[resourceId('Microsoft.Kusto/Clusters', reference('linkedTemplate').outputs['clusterNameResult'].value)]"
],
"properties": {
"principalId": "abc.def#gmail.com",
"role": "Viewer",
"principalType": "User",
"tenantId": "523547f7-9d12-45c5-9g15-2ysb44a3r2m4"
},
"copy": {
"name": "databasecopy",
"count": "[length(parameters('databaseNameList'))]"
}
}
]
I am refering to the cluster name deployed through template 1 in template 2 , specified at "dependsOn" but it fails with error The template resource 'adx-jtcjiot-dev-sea-adxdb001' at line '84' and column '9' is not valid: The template function 'reference' is not expected at this location.
Has anyone used reference functions for deployment like this, I want to keep cluster and database deployment separately as database creation might occur often at the same time i don't want to hardcode the clustername in the database template. Is there any other way to do it or to resolve this error.
Thanks in advance!
I'm not sure I understand why you want to keep those separate in the first place.
What about simply putting them together as in the example here: https://learn.microsoft.com/en-us/azure/data-explorer/automated-deploy-overview#step-3-create-an-arm-template-to-deploy-the-cluster?
Ultimately, dependsOn doesn't accept reference functions as appeared in the error message. My second thought was to find out resource name using resourceID function, but apparently that's not supported. So, instead I have defined the server name in variables and used it for database "name field"
Because you're depending on a resource being deployed in the same deployment, you don't need to define a resource id, or use a reference. You can just use the name of the resource deployment (as defined in the arm template), like this:
{
"type": "Microsoft.Resources/deployments",
"name": "linkedTemplate",
etc
},
{
"type": "Microsoft.Kusto/Clusters/Databases",
etc
"dependsOn": [
"linkedTemplate"
]
}
That will ensure that the deployment of the database will not start until the deployment of the Kusto cluster has been completed.

ARM template storage account not found while creating Azure Function App

I have an ARM template which creates a Storage Account, App Service Plan, Application Insights and a Function App. In the dependsOn section of the Function App, I configured it to be depending on the other three resources. But when I deploy the template, it fails with the following error:
Status Message: The Resource 'Microsoft.Storage/storageAccounts/dummystorage' under resource group 'dummy-rg' was not found.
I see that the Storage Account, ASP and App Insights are being created first, followed by the Function App. So it seems the dependency is being honored. It's almost like the Storage Account provisioning is not completely finished when the Function App is being created. As you can see in the template below, the Function App resource uses the listKeys to get the Storage Account keys as part of the provisioning.
Does anybody have any idea how I can prevent this from happening? I've read everything in the docs regarding dependencies, and I believe this should just work.
One remark is that the storage account is being deployed based on a condition. I can't imagine it has something to do with my issue, but I just wanted to mention it.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"functionAppName": {
"type": "string"
},
"appServicePlanName": {
"type": "string"
},
"appInsightsName": {
"type": "string"
},
"deployStorage": {
"type": "bool",
"defaultValue": true
},
"storageAccountName": {
"type": "string"
},
"utcDateTime": {
"type": "string",
"defaultValue": "[utcNow()]"
}
},
"variables": {
"storageAccountDeploymentName": "[concat('storageDeploy-', parameters('utcDateTime'))]",
},
"resources": [
{
"name": "[parameters('functionAppName')]",
"apiVersion": "2018-11-01",
"type": "Microsoft.Web/sites",
"kind": "functionapp",
"location": "[parameters('location')]",
"tags": "[parameters('tags')]",
"dependsOn": [
"[parameters('appInsightsName')]",
"[parameters('appServicePlanName')]",
"[resourceId('Microsoft.Resources/deployments', variables('storageAccountDeploymentName'))]"
],
"properties": {
"name": "[parameters('functionAppName')]",
"siteConfig": {
"appSettings": [
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~3"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "powershell"
},
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference(concat('Microsoft.Insights/components/', parameters('appInsightsName')), '2015-05-01').InstrumentationKey]"
},
{
"name": "APPLICATIONINSIGHTS_CONNECTION_STRING",
"value": "[reference(concat('Microsoft.Insights/components/', parameters('appInsightsName')), '2015-05-01').ConnectionString]"
},
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[concat(toLower(parameters('functionAppName')), '813b')]"
}
],
"use32BitWorkerProcess": false,
"powerShellVersion": "[variables('powerShellVersion')]"
},
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"clientAffinityEnabled": true
}
},
{
"name": "[parameters('appServicePlanName')]",
"apiVersion": "2018-11-01",
"type": "Microsoft.Web/serverfarms",
"location": "[parameters('location')]",
"tags": "[parameters('tags')]"
// snip
},
{
"name": "[parameters('appInsightsName')]",
"apiVersion": "2020-02-02-preview",
"type": "microsoft.insights/components",
"location": "[parameters('location')]",
"tags": "[parameters('tags')]",
// snip
},
{
"name": "[variables('storageAccountDeploymentName')]",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"condition": "[parameters('deployStorage')]",
"properties": {
"expressionEvaluationOptions": {
"scope": "outer"
},
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"name": "[parameters('storageAccountName')]",
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-06-01",
"tags": "[parameters('tags')]",
"location": "[parameters('location')]",
// snip
}
]
}
}
}
]
}
I think you're running into this: https://bmoore-msft.blog/2020/07/26/resource-not-found-dependson-is-not-working/
TLDR;
listKeys is called in a separate job in the deployment engine and gets called early if that resource (i.e. your storage) is not in the same deployment.
you don't need to nest your storageAccount deployment, doesn't hurt but doesn't help. If your storageAccount deployment is unconditional, then it will help because ARM will wait before running listKeys.
if you need the condition, the only fix is to nest the function app deployment and set a dependency on the conditional storageAccount (the dependency need not be conditional ARM will handle that). You need to set the evaluation scope to "inner" on that nested deployment.
That help?
I noticed your dependsOn for Storage looks like this:
"dependsOn": [
"[parameters('appInsightsName')]",
"[parameters('appServicePlanName')]",
"[resourceId('Microsoft.Resources/deployments', variables('storageAccountDeploymentName'))]"
Whereas my despendsOn for Storage Account and Blob Container looks like this... could that be the reason it's not working? I have never used Microsoft.Resources/deployments (FYI my ARM Template deployment is working)
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', variables('StorageAccountName'), 'default', variables('BlobContainerName2'))]",
"[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
Alternatively:
You could drop the conditional and deploy the Storage Account each time. If it exists no change, if it doesn't exist then it's created.
The only caveat to this is if you change something on the storage account post-deployment or if the Storage account has a dynamically set name based on Timestamp or GUID etc.
I realize this is a work around, but it should solve your problem barring the caveat above.

ARM template error on storage Queue Service as Optional Parameter

I was trying to deploy queue service as optional parameter with default blank value, template first create storage account then queue service as nested resource. template throwing error Message=Deployment template validation failed: 'The template resource '[concat(parameters('storageName'),'/default/',parameters('storagequeues')[copyIndex()])]'
at line '91' and column '9' is not valid: The language expression property array index '0' is out of bounds.
for some reason schema validating nested resource name before condition evaluation. Is this expected behavior ? if not please suggest work around.
I have tried with condition "condition": "[not(contains(parameters('storagequeues'),'none'))]", and Having defaultvalue="none" then it won\t create queue. it works fine but this is not desired way of doing.
This template creating a queue under a storage account may meet you need.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"metadata": {
"description": "Specifies the name of the Azure Storage account."
}
},
"queueName": {
"type": "string",
"metadata": {
"description": "Specifies the name of the blob container."
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Specifies the location in which the Azure Storage resources should be deployed."
}
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-06-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"kind": "StorageV2",
"properties": {
"accessTier": "Hot"
},
"resources": [
{
"type": "queueServices/queues",
"apiVersion": "2019-06-01",
"name": "[parameters('queueName')]",
"dependsOn": [
"[parameters('storageAccountName')]"
]
}
]
}
]
}
I have seen this 'The language expression property array index '0' is out of bounds.' error before, but the reason may different. I cannot see your defaultValue in 'storagequeue', maybe the null array cause this issue. You can refer to this.

ARM Templates for Azure Functions with many appSettings for different environments and slots

I've got two Azure Function apps that use deployment slots, stage and production. These two Azure Function apps have about 50~ key:value pairs in Application Settings to define various API keys, application behavior, connection strings, etc.
I want to deploy these two Azure Function apps to five different environments (CI, DEV, QA, STG, PROD). I believe that deploying these resources to Azure using ARM templates is the better choice over Azure CLI. I will create tasks in my Azure DevOps release pipeline to achieve this.
In order to break down the ARM template into something easily maintainable, I wanted to create an ARM template parameter file for each environment. When defining the deployment file for the Azure Function, one of the properties to define is the siteConfig object, where you define the appSettings object with a NameValuePair object. For each environment, the stage and production slot will have different API keys, connection strings, and application behavior. My deployment file creates the Azure Function app with both the production slot and stage slot. In the deployment file, I have to provide the appSettings NameValuePair object twice. Then, I have to create 5 different parameter files for each environment. Multiply that by 2 because I have two slots.
Is it also true that all parameters defined in the parameter file have to be defined in the deployment template file in the parameters object?
Can I just pass in an array of objects with NameValuePairs from the parameter file so I don't have to have the entire list of parameters defined in the deployment file at the top and also under siteConfig.appSettings for the function app?
The documentation here shows that you can only provide an array of strings or a single object with many key:values. But appSettings is an array of objects where each object has 3 key:value pairs.
This is what the resource looks like in the deployment file. I would like to simply reference an entire array of objects from the parameter file, but it looks like the documentation states that I define all 50~ parameters at the top of the deployment file, which then the parameter file overrides when executed by Azure CLI or Azure DevOps task.
{
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"name": "[parameters('function-app-name')]",
"location": "[parameters('location')]",
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"kind": "functionapp",
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"siteConfig": {
"appSettings": [] # I need to provide an array of objects here
}
}
}
In addition to my complaint...I can't believe I'm going to have to create 20 parameter files for all five environments and their two Azure Functions that have two slots. Is there a better way to deploy to all my environments and their deployment slots using ARM templates and parameter files with their unique application settings?
UPDATE:
I was able to piece together various methods for creating environment-specific ARM templates and came up with the following result, with some inconvenient problems. First, I'll explain where I am now and then bring up the problems associated with the design.
In my deployment template, I've defined two parameters. Here they are:
"deploymentEnvironment": {
"type": "string",
"allowedValues": [
"CI",
"DEV",
"QA",
"TRN",
"STG",
"PROD"
],
"metadata": {
"description": "Type of environment being deployed to. AKA \"Stage\" in release definition."
}
},
"applicationSettings": {
"type": "object",
"metadata": {
"description": "Application settings from function.parameters.json"
}
}
My function.parameters.json has a structure like this:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"applicationSettings": {
"value": {
"CI": {
"appsetting1": "",
"appsetting2": ""
},
"DEV": {
"appsetting1": "",
"appsetting2": "" },
"QA": {
"appsetting1": "",
"appsetting2": ""
}
}
}
}
}
For each environment, I had placed all of my connection strings, apikeys, and application settings.
For the production slot for the function app, you can add a "resources" property which applies configuration to it. Here is the entire function app deployment:
{
"name": "[parameters('function-app-name')]",
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"kind": "functionapp",
"location": "[parameters('location')]",
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Insights/components/', variables('applicationInsightsName'))]",
"[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
"[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
],
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2018-11-01",
"properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
]
}
]
}
Next up was defining the stage slot deployment resource. Here it is:
{
"type": "Microsoft.Web/sites/slots",
"apiVersion": "2018-11-01",
"name": "[concat(parameters('function-app-name'), '/stage')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
],
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"kind": "functionapp",
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]"
},
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2018-11-01",
"properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]",
"[resourceId('Microsoft.Web/sites/slots/', parameters('function-app-name'), 'stage')]"
]
}
]
}
With this solution, I don't have to have a bunch of parameters.json files for each environment.
The problems...
Defining all of the application settings in parameters.json means I can't use template functions to get connection strings or Azure Key Vault values.
This is when I started to move some of the application settings to the deployment template to use template functions. So instead of having the APPINSIGHTS_INSTRUMENTATIONKEY and other AzureWebJobs* application settings in the parameters.json file, I provided the siteConfig object in the "properties" object for the Microsoft.Web/Sites resource and the Microsoft.Web/Sites/Slots resource.
This is the real bummer - When the deployment ran, it applied the siteConfig.appsettings values with the function app, then when it applied the parameters.json file, it deleted the application settings and applied only the ones from the json, instead of merging them together. That was a HUGE disappointment. In my initial testing with the AzureCLI, I used this command az functionapp config appsettings set --name $functionAppName --resource-group $resourceGroupName --settings $settingsFile --slot $slot to test what would happen with application settings that were not in a json file and was happy that it never deleted application settings. The powershell command gets and sets the values, merging it nicely and never deleting. But the ARM API deletes all those name value pairs and applies only what is defined. This means I cannot use template functions to create dynamic application settings and a json file to apply static application settings.
As of now, I feel like the only way to do a decent ARM template deployment is just deploy the resources without the siteConfig object or the config resource to apply application settings and then follow up with the Azure CLI to deploy the application settings. I suppose I could learn how to retrieve Key Vault secrets using Azure CLI or Azure DevOps pipeline tasks, but it would be even better to just have it all in a single ARM template.
For reference, here is my entire deployment template when I attempted to use dynamically generated appSettings and the config resource to define more appsettings.
{
"$schema": "https://schema.management.azure.com/schemas/2019-08-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"function-app-name": {
"defaultValue": "functionappname",
"type": "String",
"metadata": {
"description": "The name of the function app that you wish to create."
}
},
"sku": {
"type": "string",
"allowedValues": [
"S1",
"S2",
"S3"
],
"defaultValue": "S3",
"metadata": {
"description": "The pricing tier for the hosting plan."
}
},
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"metadata": {
"description": "Storage Account type"
}
},
"location": {
"type": "string",
"defaultValue": "southcentralus",
"metadata": {
"description": "Location for all resources."
}
},
"deploymentEnvironment": {
"type": "string",
"allowedValues": [
"CI",
"DEV",
"QA",
"TRN",
"STG",
"PROD"
],
"metadata": {
"description": "Type of environment being deployed to."
}
},
"applicationSettings": {
"type": "object",
"metadata": {
"description": "Application settings from function.parameters.json"
}
}
},
"variables": {
"storageAccountName": "[concat('store', uniquestring(resourceGroup().id))]",
"appServicePlanName": "[concat('ASP-', uniquestring(resourceGroup().id))]",
"applicationInsightsName": "[concat('appInsights-', uniquestring(resourceGroup().id))]",
"projectName": "DV"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-04-01",
"name": "[variables('storageAccountName')]",
"kind": "Storage",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('storageAccountType')]"
},
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
}
},
{
"name": "[variables('appServicePlanName')]",
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2019-08-01",
"location": "[parameters('location')]",
"properties": {
},
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"sku": {
"Name": "[parameters('sku')]",
"capacity": 2
},
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
]
},
{
"name": "[variables('applicationInsightsName')]",
"apiVersion": "2015-05-01",
"type": "Microsoft.Insights/components",
"kind": "web",
"location": "[parameters('location')]",
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"properties": {
"Application_Type": "web"
}
},
{
"name": "[parameters('function-app-name')]",
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"kind": "functionapp",
"location": "[parameters('location')]",
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
"siteConfig": {
"appSettings": [
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference(concat('microsoft.insights/components/', variables('applicationInsightsName'))).InstrumentationKey]"
},
{
"name": "AzureWebJobsDashboard",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~1"
}
]
}
},
"dependsOn": [
"[resourceId('Microsoft.Insights/components/', variables('applicationInsightsName'))]",
"[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
"[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
],
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2018-11-01",
"properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
]
}
]
},
{
"type": "Microsoft.Web/sites/slots",
"apiVersion": "2018-11-01",
"name": "[concat(parameters('function-app-name'), '/stage')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
],
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"kind": "functionapp",
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
"siteConfig": {
"appSettings": [
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference(concat('microsoft.insights/components/', variables('applicationInsightsName'))).InstrumentationKey]"
},
{
"name": "AzureWebJobsDashboard",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~1"
}
]
},
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2018-11-01",
"properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
]
}
]
}
}
]
}
Update 2:
I raised a github issue to have them fix the problem with ARM templates replacing all of the application settings on each deployment.
FWIW - I also voted on some Azure feedback post.
Sorry I don't have a huge amount of time to answer, and you have a bunch of questions which relate mainly to "what's the best way to...", and the answer is always "it depends".
One thing I find easier to manage is instead of using siteConfig to set all the app settings, you can create a top level resource of type Microsoft.Web/sites/config (which I find useful sometimes as you can create them after the site is created, so if you have dependencies elsewhere that aren't setup yet, it can be handy to separate out the config and the site).
"parameters": {
"appSettings": {
"type": "object",
"defaultValue": {
"property1": "value1",
"property2": "value2"
}
}
}
"resources": [
{
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"name": "[parameters('function-app-name')]",
"location": "[parameters('location')]",
"kind": "functionapp",
"properties": {
"enabled": true,
"serverFarmId": "..."
}
},
{
"type": "Microsoft.Web/sites/config",
"name": "[concat(parameters('function-app-name'), '/appsettings')]",
"apiVersion": "2018-11-01",
"properties": "[parameters('appSettings')]"
"dependsOn": [ "[resourceId('Microsoft.Web/sites/sites', parameters('function-app-name'))]",
}
]
One of the drawbacks of the above, is that you can't use certain functions in the params section, so you can't use listKeys() to get a key to a resource, so it's only useful sometimes, or like this example, if you wanted to add a reference to app insights which is also created in the same template, this isn't possible if you're passing in the settings as a param.
{
"type": "Microsoft.Web/sites/config",
"name": "[concat(parameters('function-app-name'), '/appsettings')]",
"apiVersion": "2018-11-01",
"properties": {
"property1": "value1",
"property2": "value2",
"APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('microsoft.insights/components/', variables('appInsightsName')), '2015-05-01').InstrumentationKey]"
}
"dependsOn": [
"[resourceId('Microsoft.Web/sites/sites', parameters('function-app-name'))]",
"[resourceId('microsoft.insights/components', variables('appInsightsName'))]"
}
You should really be resolving everything you can at deploy time, so a storage account (for example) connection string can be added into the template securely, and resolved at deploy time only.
Another handy tip, is to use key vault to store any secure credentials, api keys, connection strings etc that cannot be resolved in the template. You mention needing them, but then you're committing them to source control in the templates... Well, they wont stay secret very long (another tip, ensure they all use securestring instead of string types, otherwise the portal will expose them in the deployment logs for the resource group). You can access key vaults from app settings like this:
"secretConnectionString": "[concat('#Microsoft.KeyVault(SecretUri=https://', variables('vaultName'), '.vault.azure.net/secrets/my-connection-string/)')]",
But for the above to work, you will need to give your application read access to the vault "vaultName", which should be fine if you use managed service identities.
It is possible to combine static configuration with deployment-time references.
You use the union template function to combine your static configuration (object or array) with some deployment-time value that you wrap using the json template function.
In the example below I combine:
a static base config object
a static service specific config object
a deployment-time Application Insights config value
[union(
variables('appServiceBaseConfig'),
variables('appService1'),
json(
concat(
'{\"APPINSIGHTS_INSTRUMENTATIONKEY\":\"',
reference(concat('microsoft.insights/components/', variables('applicationInsightsName')), '2015-05-01').InstrumentationKey,
'\"}')
)
)
]
For anyone who comes to this thread, I wanted to offer an alternative to the suggestion above of using the union. I first went with that option but started to find it hard to use the string concatenation forming the json so wanted to offer this alternative:
[union(
variables('appServiceBaseConfig'),
variables('appService1'),
createObject(
'APPINSIGHTS_INSTRUMENTATIONKEY', reference(concat('microsoft.insights/components/', variables('applicationInsightsName')), '2015-05-01').InstrumentationKey,
'MY_VALUE', 'xyz'
)
)
]
The reason for this additional suggested answer is that the createObject function makes it much easier to construct the object if you have multiple keys values pairings.
Note: Multi-line functions like this are ONLY currently supported in ARM if you are using the Powershell cli as per this note in the docs. Took me a while to figure this out trying to deploy in Azure DevOps :facepalm:
To answer this piece:
Is it also true that all parameters defined in the parameter file have
to be defined in the deployment template file in the parameters
object?
Yes everything in the parameters file needs to be defined in the deployment file. The opposite is not true. Everything defined in your deployment file does not need to be defined in your parameters file. The definition in the deployment file can have a default value:
"location": {
"type": "string",
"defaultValue": "Central US",
"metadata": {
"description": "Specifies the Azure location where the key vault should be created."
}
},
Alternatively a parameter can be passed in as an override parameter in a release task.
just adding input to this thread to add my solution for this issue. I'm not sure if the overwriting App Settings issue has been fixed in normal ARM Templates, but the release pipeline step allows you to set additional app settings that get deployed when the app gets deployed. So, I have the dynamic app settings/the app settings that all my azure functions need defined on creation set in the ARM Template, and then I have additional app settings set in the release pipeline (using a variable group with secret variables to hide their values). Like so:

Azure availability zone parameter syntax

I'm trying to parameterise a VM deployment that uses availability zones. However, I keep receiving this error on deployment:
'The provided value for the template parameter 'availabilityZoneParameter' at line '1' and column '5118' is not valid.'
or:
"Deployment template parse failed: 'Error converting value \"[ '1' ]\" to type 'System.String[]'. Path ''.'."
The parameter file syntax is currently:
"availabilityZoneParameter": {
"value": "[ '1' ]"
}
I am then porting it in as a parameter and turning it into a variable, before exporting it to other linked templates as well as using it in the initial build template.
Parameter in deploy file syntax:
"availabilityZoneParameter": {
"type": "string"
}
Variable in original deploy file syntax:
"availabilityZone": "[parameters('availabilityZoneParameter')]"
Disk creation syntax in original deploy file:
{
"name": "[variables('diskName')]",
"type": "Microsoft.Compute/disks",
"apiVersion": "2017-03-30",
"location": "[resourceGroup().location]",
"zones": [ "[variables('availabilityZone')]" ],
"sku": {
"name": "Standard_LRS"
},
"properties": {
"creationData": {
"createOption": "Empty"
},
"diskSizeGB": 1023
}
},
VM parameter in original deploy template, which feeds into linked template:
"name": "PAN-VM",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"dependsOn": [
"[concat('Microsoft.Compute/disks/', variables('diskName'))]",
"Microsoft.Resources/deployments/SettingUpVirtualNetwork",
"Microsoft.Resources/deployments/SettingUpPublicIP",
"Microsoft.Resources/deployments/SetupNetworkInterfaces"
],
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[concat(variables('virtualMachineTemplate'), parameters('artifactsLocationSasToken'))]",
"contentVersion": "1.0.0.5"
},
"parameters": {
"avZone": {
"value": "[variables('availabilityZone')]"
VM template parameter:
"avZone": {
"type": "string"
VM template variable:
"variables": {
"apiVersion": "2018-04-01",
"availabilityZone": "[parameters('avZone')]"
VM template resource (calling parameter):
"resources": [
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Compute/virtualMachines",
"name": "[parameters('vmName')]",
"location": "[parameters('location')]",
"zones": "[variables('availabilityZone')]",
"plan": {
"name": "[parameters('imageSku')]",
"product": "[parameters('imageOffer')]",
"publisher": "[parameters('imagePublisher')]"
},
"properties":
For context - there are several files at play here. An initial azureparameters file, an azuredeploy file, and then at least two linked templates which also rely on the availability zone value.
Any advice on the correct syntax?
According to the example I've found online, it should be like this:
"availabilityZoneParameter": {
"value": [ "1" ]
}
also, it should be array:
"availabilityZoneParameter": {
"type": "array"
}
As it excepts an array, not a string that looks like an array:
https://github.com/Azure/azure-quickstart-templates/blob/master/101-vm-simple-zones/azuredeploy.json#L176
Should the parameter just be ?
"availabilityZoneParameter": {
"value": "1"
}
Final syntax, for those coming to this board seeking the same answer:
Note that the value is an array and not a string, as pointed out by contributor 4c74356b41 in this thread.
In original azureparameter file:
},
"availabilityZone": {
"value": [ "3" ]
}
In azuredeploy file:
},
"availabilityZone": {
"type": "array"
}
To call the availability zone parameter in nested template (example using storage disk resource):
"name": "[variables('diskName')]",
"type": "Microsoft.Compute/disks",
"apiVersion": "2017-03-30",
"location": "[resourceGroup().location]",
"zones": "[parameters('availabilityZone')]",
"sku": {
If using a linked template, when expressing the linked template parameters, I used this syntax:
"avZone": {
"value": "[parameters('availabilityZone')]"
Importing the parameter in the linked template:
},
"avZone": {
"type": "array"
}
And then in the resources within the linked template, I called the parameter in the same way as the azuredeploy template:
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Compute/virtualMachines",
"name": "[parameters('vmName')]",
"location": "[parameters('location')]",
"zones": "[parameters('avZone')]",
As you can see, I decided not to turn it into a variable as this was unnecessary in my case.

Resources