(400) Bad Request when deploying a Function App via ARM - azure

Hopefully, someone can tell me what I'm doing wrong with this ARM template deployment.
Using the template at the bottom of the question, I can deploy a Function App - with app service plan and storage account - but I get the following error.
STATUS BadRequest
PROVISIONING STATE Failed
TIMESTAMP 4/19/2017, 1:33:00 PM
DURATION 1 second
TYPE Microsoft.Web/sites/config
RESOURCE ID /subscriptions/blah-blah-blah/resourceGroups/blah/providers/Microsoft.Web/sites/functionname/config/appsettings
STATUSMESSAGE {
"Code": "BadRequest",
"Message": "There was a conflict. The remote server returned an error: (400) Bad Request.",
"Target": null,
"Details": [
{
"Message": "There was a conflict. The remote server returned an error: (400) Bad Request."
},
{
"Code": "BadRequest"
},
{
"ErrorEntity": {
"ExtendedCode": "01020",
"MessageTemplate": "There was a conflict. {0}",
"Parameters": [
"The remote server returned an error: (400) Bad Request."
],
"Code": "BadRequest",
"Message": "There was a conflict. The remote server returned an error: (400) Bad Request."
}
}],
"Innererror": null
}
RESOURCE functionname/appsettings
If I remove this AppSetting property from the function app part of the template then the deployment works fine.
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('Storage_Account_Name'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('Storage_Account_Name')),'2015-05-01-preview').key1)]",
But then when I go to the deployed function app I get this error popup.
Error:
'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' application setting is missing from your app. This setting contains a connection string for an Azure Storage account that is used to host your functions content. Your app will be completely broken without this setting. You may need to delete and recreate this function app if you no longer have access to the value of that application setting.
I also note that if I try and add that appsetting in the portal manually after this successful deployment, I get the same error as the initial deployment.
So, I don't know if I'm putting it in the template incorrectly, or if something in the Azure Deployment for functions apps is broken.
Where am I going wrong?
Template
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"App_Service_Plan_Name": {
"defaultValue": "aspname",
"type": "String"
},
"Functions_App_Name": {
"defaultValue": "funcname",
"type": "String"
},
"Storage_Account_Name": {
"defaultValue": "storagename",
"type": "String"
}
},
"variables": {},
"resources": [
{
"comments": "Deployed from template",
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('Storage_Account_Name')]",
"apiVersion": "2016-01-01",
"sku": {
"name": "Standard_LRS"
},
"location": "[resourceGroup().location]",
"kind": "Storage"
},
{
"comments": "Deployed from template",
"type": "Microsoft.Web/serverfarms",
"sku": {
"name": "Y1",
"tier": "Dynamic",
"size": "Y1",
"family": "Y",
"capacity": 0
},
"kind": "functionapp",
"name": "[parameters('App_Service_Plan_Name')]",
"apiVersion": "2015-08-01",
"location": "[resourceGroup().location]",
"properties": {
"name": "[parameters('App_Service_Plan_Name')]",
"numberOfWorkers": 0
},
"dependsOn": []
},
{
"comments": "Deployed from template",
"type": "Microsoft.Web/sites",
"kind": "functionapp",
"name": "[parameters('Functions_App_Name')]",
"apiVersion": "2015-08-01",
"location": "[resourceGroup().location]",
"properties": {
"name": "[parameters('Functions_App_Name')]",
"hostNames": [
"[concat(parameters('Functions_App_Name'),'.azurewebsites.net')]"
],
"enabledHostNames": [
"[concat(parameters('Functions_App_Name'),'.azurewebsites.net')]",
"[concat(parameters('Functions_App_Name'),'.scm.azurewebsites.net')]"
],
"hostNameSslStates": [
{
"name": "[concat(parameters('Functions_App_Name'),'.azurewebsites.net')]",
"sslState": 0,
"thumbprint": null,
"ipBasedSslState": 0
},
{
"name": "[concat(parameters('Functions_App_Name'),'.scm.azurewebsites.net')]",
"sslState": 0,
"thumbprint": null,
"ipBasedSslState": 0
}
],
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('App_Service_Plan_Name'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', parameters('App_Service_Plan_Name'))]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('Storage_Account_Name'))]"
],
"resources": [
{
"apiVersion": "2015-08-01",
"name": "appsettings",
"type": "config",
"dependsOn": [
"[resourceId('Microsoft.Web/Sites', parameters('Functions_App_Name'))]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('Storage_Account_Name'))]"
],
"properties": {
"FUNCTIONS_EXTENSION_VERSION": "~1",
"AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('Storage_Account_Name'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('Storage_Account_Name')),'2015-05-01-preview').key1)]",
"AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('Storage_Account_Name'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('Storage_Account_Name')),'2015-05-01-preview').key1)]",
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('Storage_Account_Name'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('Storage_Account_Name')),'2015-05-01-preview').key1)]",
"WEBSITE_CONTENTSHARE": "[concat(parameters('Functions_App_Name'),'_files')]",
"WEBSITE_NODE_DEFAULT_VERSION": "6.5.0"
}
},
{
"apiVersion": "2015-08-01",
"name": "web",
"type": "config",
"dependsOn": [
"[resourceId('Microsoft.Web/Sites', parameters('Functions_App_Name'))]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('Storage_Account_Name'))]"
],
"properties": {
"cors": {
"allowedOrigins": [
"[concat('https://',parameters('Storage_Account_Name'),'.blob.core.windows.net')]"
]
}
}
}
]
}
]
}

So typically, after spending a day or so trying to find out what's wrong before breaking down and resorting to asking SO, as soon as I do I find out what's wrong.
The issue is not the WEBSITE_CONTENTAZUREFILECONNECTIONSTRING but the WEBSITE_CONTENTSHARE. This has an underscore in it and underscores are not allowed in the naming convention of Storage Account File shares.
So, mental note, when I see an error like There was a conflict..., think ...with a naming convention, not ...with an existing resource.
I did also have to add the 3 Azure URLs to the CORS AllowedOrigins section
"https://functions.azure.com",
"https://functions-staging.azure.com",
"https://functions-next.azure.com"

Not sure if it's directly related to the issue, but I suggest making your template more similar to what the Portal uses by default. To see this:
Start the process of creating a new function app in Azure Portal
Enter some arbitrary App name and Resource Group
Click at the 'Automation options' link at the bottom
You'll get the full strict that it uses. It looks like this:
{
"parameters": {
"name": {
"type": "string"
},
"storageName": {
"type": "string"
},
"location": {
"type": "string"
},
"subscriptionId": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "2016-03-01",
"name": "[parameters('name')]",
"type": "Microsoft.Web/sites",
"properties": {
"name": "[parameters('name')]",
"siteConfig": {
"appSettings": [
{
"name": "AzureWebJobsDashboard",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('storageName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')), '2015-05-01-preview').key1)]"
},
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('storageName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')), '2015-05-01-preview').key1)]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~1"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('storageName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')), '2015-05-01-preview').key1)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[concat(toLower(parameters('name')), 'a66e')]"
},
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "6.5.0"
}
]
},
"clientAffinityEnabled": false
},
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageName'))]"
],
"location": "[parameters('location')]",
"kind": "functionapp"
},
{
"apiVersion": "2015-05-01-preview",
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('storageName')]",
"location": "[parameters('location')]",
"properties": {
"accountType": "Standard_LRS"
}
}
],
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
"contentVersion": "1.0.0.0"
}

TLDR;
Check if the storage account name is correct & only in lowercase letters.
Longer version
github issue - https://github.com/Azure/azure-cli/issues/14518#issuecomment-665255337
In my scenario I was setting up a new Resource Group, new App Service Plan & new Function - however with existing Storage Account and ran into this issue There was a conflict. The remote server returned an error: (400) Bad Request.
My issue was with my parameter file that had a casing issue in the Storage Account name. myownstorageaccountTest instead of myownstorageaccounttest
The ARM template deployment was attempting to create a new storage account with this name myownstorageaccountTest when this myownstorageaccounttest already exists. Storage account names need to be unique across azure.
Storage Account Name requirement:
The name must be unique across all existing storage account names in Azure. It must be 3 to 24 characters long, and can contain only lowercase letters and numbers.

TL;DR: Function App name must be 59 chars or less
Because these names form a hostname, I typically append random digits to fill out to the max of whatever the limit is, which I believe is documented to be 60 characters.
During the Function App creation wizard, entering a name of 61 characters properly rejects the name right in the form:
The error message of "Must be fewer than 60" is the same as "59 or less", but entering exactly 60 - which I've seen elsewhere - allows the process to continue:
Continuing to create the Function App eventually fails with this error:
{
"code": "DeploymentFailed",
"message": "At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.",
"details": [
{
"message": "There was a conflict. The remote server returned an error: (400) Bad Request."
}
]
}
Using a name of 59 chars creates OK.
This feels like a fencepost error in the form, where it accepts entry of a name that's one character larger than downstream processes will accept.

My Azure Deployment especially for the Function App also fails due to these appsettings. Here is an excerpt of my ARM script.
"siteConfig": {
"appsettings": [
{
"name": "AzureWebJobsDashboard",
"value": "[Concat('DefaultEndpointsProtocol=https;AccountName=',variables('StorageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]"
},
]
The value is used for the following settings: AzureWebJobsDashboard, AzureWebJobsStorage, WEBSITE_CONTENTAZUREFILECONNECTIONSTRING.
Depends on for my storage account is also set in the ARM script.
I'm starting the deployment via Visual Studio Team Services. Here I get my mistake. I get the error message directly from Azure.
Resource: Azure-Function-Dev
Type: Microsoft.Web/sites
Status: BadRequest
{
"error": {
"code": "InternalServerError",
"message": "There was an unexpected InternalServerError. Please try again later. x-ms-correlation-request-id: 44444-4444..."
}
}
If the Function App is redeployed again - without any changes - the deployment always works. Strange behaviour.
I then set my value to a fixed value using my parameters. Since then the deployment runs without problems. This may not be a technically clean solution, but the error can be analyzed
"siteConfig": {
"appsettings": [
{
"name": "AzureWebJobsStorage",
"value": "[parameters('AzureWebJobsStorage')]"
}, ]

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 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 - Set WebSocket On from ARM json template

I'm trying to turn WebSockets On for an Azure WebApp from an Azure ARM json template that deploys my whole infrastructure.
Here is an extract with regards to the Azure Web App. It doesn't work, i.e the WebSockets are still Off. I unsuccessfully tried different spelling: webSocketsEnabled or WebSockets.
"resources":[
{
"name": "[variables('MyApp')]",
"type": "Microsoft.Web/sites",
"location": "Brazil South",
"apiVersion": "2016-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('MyAppPlanBrazil'))]"
],
"tags": {
"[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', variables('MyAppPlanBrazil')))]": "Resource",
"displayName": "MyAppAppBrazil"
},
"properties": {
"name": "[variables('MyAppPlanBrazil')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('MyAppPlanBrazil'))]",
"siteConfig": {
"AlwaysOn": true,
"webSocketsEnabled": true,
"connectionStrings": [
{
...
},
{
...
},
]
}
}
]
UPDATE
As suggested in answer below I updated the apiVersion to "2016-08-01" but this still doesn't work.
Also note that while my schema is the one described here, apiVersion is squiggled in VS and it says the authorized value is "2015-08-01" only.
UPDATE2
I tried the solutions below. They work for their authors but not for me. I guess the problem is elsewhere. My infrastructure is already deployed and I try to update it with webSocketsEnabled. Whereas in the solution below I imagine the authors directly create the web app with webSocketsEnabled.
Also, I coupled webSocketsEnabled with alwaysOn whereas the pricing tier of my webapp doesn't allow "AlwaysOn" (as it says in the portal I need to upgrade to use that feature) so I'll try without alwaysOn.
UPDATE3
At the end, the above template worked when I removed AlwaysOn.
Thank you to those who tried to help me.
Set your api version to this: "2016-08-01"
Use
"webSocketsEnabled": true
This is from the Microsoft.Web/sites template reference:
https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites
The api version you are using (2015-08-01) from:
https://github.com/Azure/azure-resource-manager-schemas/blob/master/schemas/2015-08-01/Microsoft.Web.json
Doesn't have web sockets in it, but the later one:
https://github.com/Azure/azure-resource-manager-schemas/blob/master/schemas/2016-08-01/Microsoft.Web.json
Does have webSocketsEnabled.
Please have a try to use the following code. It works correctly on my side.
Updated: add whole test arm template and you could have a try to use the following code with your service plan name and resource group name
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"serverFarmName": {
"type": "string",
"defaultValue": "YourPlan"
},
"serverFarmResourceGroup": {
"type": "string",
"defaultValue": "ResourceGroupName"
}},
"variables": {
"ARMtemplateTestName": "[concat('ARMtemplateTest', uniqueString(resourceGroup().id))]"},
"resources": [
{
"name": "[variables('ARMtemplateTestName')]",
"type": "Microsoft.Web/sites",
"location": "southcentralus",
"apiVersion": "2015-08-01",
"dependsOn": [ ],
"tags": {
"[concat('hidden-related:', resourceId(parameters('serverFarmResourceGroup'), 'Microsoft.Web/serverFarms', parameters('serverFarmName')))]": "Resource",
"displayName": "ARMtemplateTest"
},
"properties": {
"name": "[variables('ARMtemplateTestName')]",
"serverFarmId": "[resourceId(parameters('serverFarmResourceGroup'), 'Microsoft.Web/serverFarms', parameters('serverFarmName'))]"
},
"resources": [
{
"name": "web",
"type": "config",
"apiVersion": "2015-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('ARMtemplateTestName'))]"
],
"tags": {
"displayName": "enableWebSocket"
},
"properties": {
"webSocketsEnabled": true,
"alwaysOn": true
}
},
{
"apiVersion": "2015-08-01",
"name": "connectionstrings",
"type": "config",
"dependsOn": [
"[resourceId('Microsoft.Web/Sites', variables('ARMtemplateTestName'))]"
],
"properties": {
"ConnString1": {
"value": "My custom connection string",
"type": "custom"
},
"ConnString2": {
"value": "My SQL connection string",
"type": "SQLAzure"
}
}
},
{
"name": "appsettings",
"type": "config",
"apiVersion": "2015-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('ARMtemplateTestName'))]"
],
"tags": {
"displayName": "Appsetting"
},
"properties": {
"key1": "value1",
"key2": "value2"
}
}
]
}],
"outputs": {}
}
Test Result:
All the above solution should work.
My initial snippet worked as well ... as soon as I removed alwaysOn.
Indeed, I was using a free tiers App Service Plan for which alwaysOn is not available. While there was no errors or anything else indicating something wrong, I could not set webSocketEnabled and alwaysOn at the same time in that case.

Arm Template conditional output parameters

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.

Resources