Azure Function+ARM: merge app settings with current settings - azure

My deployment is split into two pipelines
deploy infrastructure (run ARM template)
deploy & configure application (upload app, run script)
My ARM template contains an AppSettings array, like this:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
// ...
},
"variables": {
"functionAppName": "[parameters('appName')]",
"storageAccountid": "[concat(resourceGroup().id,'/providers/','Microsoft.Storage/storageAccounts/', parameters('storageAccountName'))]"
},
"resources": [
{
"apiVersion": "2015-08-01",
"type": "Microsoft.Web/sites",
"name": "[variables('functionAppName')]",
"location": "[parameters('location')]",
"kind": "functionapp",
"dependsOn": [],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]",
"siteConfig": {
"appSettings": [
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[toLower(variables('functionAppName'))]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~2"
},
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "6.5.0"
},
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference(resourceId('microsoft.insights/components/', parameters('applicationInsightsName')), '2015-05-01').InstrumentationKey]"
}
]
}
}
}
]
}
During the application deployment, I set new app settings, like this:
az functionapp config appsettings set --resource-group $resourceGroupName
--name $functionAppName --settings "foo=bar"
Whenever the infrastructure pipeline is run, it completely removes all app settings which were added via script (e.g. foo). Is there a way to tell ARM to "merge" the deployed AppSettings with the settings defined by the template? Ideally, this should also work when deploying the ARM template for the very first time.
My current workaround is to simply remove the AppSettings part of the ARM template completely.

No, you cannot have merge behaviour. I dont think you can set individual app setting values with arm templates, so you'd need to put those keys you add at deployment time to the arm template, or remove appsettings from the arm template.

Related

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.

Deploying ARM templates from Azure Release Pipelines removes code

According to documentation "An ARM template is idempotent, which means it can be executed as many times as you wish, and the result will be the same every time". But I just learned that when I redeploy AppService (without any changes) it removes my application. Endpoints were not responding anymore and there was no application logs so I went to Azure portal console, ran DIR, and to my surprise the only file that is there is hostingstart.html! Is it documented somewhere? This changes completely how I need to handle ARM templates in my Release pipeline.
I'm using linked templates. In main template I have this resource:
{
"name": "myApp",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"dependsOn": [
"storage"
],
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[uri(variables('templateBaseUrl'), 'myApp.json')]"
},
"parameters": {
"env": {
"value": "[parameters('env')]"
},
"myAppAppServiceSku": {
"value": "[parameters('myAppAppServiceSku')]"
},
"storageAccountName": {
"value": "[variables('storageAccountName')]"
}
}
}
}
and the linked template
"resources": [
{
"name": "[variables('myAppServerFarmName')]",
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2018-02-01",
"location": "[resourceGroup().location]",
"tags": {
"ENV": "[parameters('env')]"
},
"sku": {
"name": "[parameters('myAppAppServiceSku')]"
},
"properties": {
}
},
{
"name": "[variables('myAppWebSiteName')]",
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"dependsOn": [
"[variables('myAppServerFarmName')]"
],
"location": "[resourceGroup().location]",
"tags": {
"ENV": "[parameters('env')]"
},
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('myAppServerFarmName'))]",
"siteConfig": {
"alwaysOn": true
}
},
"resources": [
{
"name": "appSettings",
"type": "config",
"apiVersion": "2018-11-01",
"dependsOn": [
"[variables('myAppWebSiteName')]"
],
"tags": {
"ENV": "[parameters('env')]"
},
"properties": {
"storageAccountName": "[parameters('storageAccountName')]",
"storageKey": "[listKeys(resourceId('Microsoft.Storage/storageAccounts/', parameters('storageAccountName')), '2019-04-01').keys[0].value]"
}
}
]
}
]
EDIT:
I have checked with deployment using Kudu ZIP Deploy. And after this deployment it redeployment of ARM templates does not remove code! So it works as expected. So deployment from Release Pipelines is different in some way.
After I execute both steps everything looks fine. But when I then just execute first step application code is removed.
This is how it looks right now.
And steps have one task each:
See Azure Functions ARM template redeployment deletes my published functions for more info. Essentially you need to add this to your template:
{ "name": "WEBSITE_RUN_FROM_PACKAGE", "value": "1" }

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 Web App staging slot App Settings missing on ARM template deployment

I am working on ARM templates for some web application deployments. These templates set up a web application along with a staging slot for this application. The way I wish to handle this is to only deploy the app settings to the staging slot. By doing this I can handle rollbacks nicely as the app settings will swap with the slot and have any old values tied to the app they were deployed with. However upon deploying these ARM templates successfully I can see there are no app settings deployed to my staging slots other than website_node_default_version.
Below is a snippet of the ARM templates showing one pair of web app + staging slot:
{
"type": "Microsoft.Web/sites",
"apiVersion": "2016-08-01",
"name": "[variables('egressAppName')]",
"location": "[parameters('location')]",
"kind": "app",
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"clientAffinityEnabled": true
}
},
{
"type": "Microsoft.Web/sites/slots",
"apiVersion": "2016-08-01",
"name": "[concat(variables('egressAppName'),'/','staging')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('egressAppName'))]"
],
"kind": "app",
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"clientAffinityEnabled": true,
"appSettings": [
{
"name": "APP_INSIGHTS_INSTRUMENTATION_KEY",
"value": "[reference(resourceId(variables('rsg') ,'microsoft.insights/components/', variables('ainName')), '2015-05-01').InstrumentationKey]"
},
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "[parameters('netCoreEnvironment')]"
},
{
"name": "MONITOR_API_ACCOUNT_RATE_LIMIT",
"value": "[parameters('accountRateLimit')]"
},
{
"name": "MONITOR_API_ACCOUNT_RATE_LIMIT_WINDOW_SECONDS",
"value": "[parameters('accountLimitWindow')]"
},
{
"name": "MONITOR_API_IP_RATE_LIMIT",
"value": "[parameters('ipLimit')]"
},
{
"name": "MONITOR_API_IP_RATE_LIMIT_WINDOW_SECONDS",
"value": "[parameters('ipLimitWindow')]"
},
{
"name": "MONITOR_DATA_EXPLORER_INSTANCE_NAME",
"value": "[variables('dataExplorerName')]"
},
{
"name": "MONITOR_DATA_EXPLORER_REGION",
"value": "[parameters('location')]"
},
{
"name": "MONITOR_DIAG_ACCOUNT_APP_ID",
"value": "[parameters('diagAccountAppID')]"
},
{
"name": "MONITOR_DIAG_ACCOUNT_APP_SECRET_LOCATION",
"value": "[parameters('diagAccountAppSecret')]"
},
{
"name": "MONITOR_EGRESS_APP_TENANT",
"value": "[parameters('egressTenantID')]"
},
{
"name": "MONITOR_KEY_VAULT_APP_ID",
"value": "[parameters('keyVaultAppID')]"
},
{
"name": "MONITOR_KEY_VAULT_APP_SECRET",
"value": "[parameters('keyVaultAppSecret')]"
},
{
"name": "MONITOR_KEY_VAULT_NAME",
"value": "[variables('keyVaultName')]"
},
{
"name": "MONITOR_PGSQL_CONNECTION_STRING",
"value": "[parameters('postgresConnectionString')]"
},
{
"name": "MONITOR_TOKEN_VALIDATION_CLOCK_SKEW_SECONDS",
"value": "[parameters('validationClockSkew')]"
},
{
"name": "WEBSITE_RUN_FROM_PACKAGE",
"value": "1"
}
]
}
}
I have tried deleting just the staging slots and redeploying as well as deleting all the web apps and redeploying with no luck. At this point I'm just hoping to get more eyes over this to see if I've missed anything obvious. I'm deploying these ARM templates through Azure DevOps release pipelines using the Azure Resource Group Deployment task.
The appSettings should be defined in the SiteConfig object instead of Microsoft.Web/sites/slots properties as your template. To create a Microsoft.Web/sites/slots/config resource for app settings, you could add the JSON to the resources section of your template.
Also, you can see the Monitoring and diagnostic settings could be swapped here.

Override appSettings of existing azure function app via ARM templates

I have developed an azure timer trigger function. I am taking timer schedule from appSettings of the function app as following.
function.json
This is working fine for given static schedule. But this schedule should be able to change as per the user requirement from another web application, when user need to change the schedule.
I am struggling to change schedule parameter from external application dynamically. What i was tried is deploy an ARM templatere injecting new schedule values from following ARM template.
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"type": "String"
},
"location": {
"type": "String"
},
"subscriptionId": {
"type": "String"
},
"schedule1": {
"type": "String"
},
"schedule2": {
"type": "String"
}
},
"resources": [
{
"type": "Microsoft.Web/sites",
"kind": "functionapp",
"name": "[parameters('name')]",
"apiVersion": "2016-03-01",
"location": "[parameters('location')]",
"properties": {
"name": "[parameters('name')]",
"siteConfig": {
"appSettings": [
{
"name": "schedule1",
"value": "[parameters('schedule1')]"
},
{
"name": "schedule2",
"value": "[parameters('schedule2')]"
}
]
},
"clientAffinityEnabled": false,
"reserved": false
}
}
]
}
However, this is not overriding existing appSettings. Instead, it returns an error "Web site already exists"
Is there any method to override appSettings as explained above and restart the function app in order to affect new appSettings parameters.
Per my test, your template works fine on my side.
Just some information for you to refer.
This is my function app:
Test the template in the portal:
Deploy result:
Check in the portal:
Note: It will overwrite all the settings of the app, before the deployment, there were other settings in my app, currently, there are just two settings.
Besides, instead of using ARM template, I recommend you to use REST API, essentially, the template is also calling API. You could also use powershell to do it, here is a similar post, you could refer to it.
For anyone coming across this, it is possible to override the Microsoft.Web/sites resource's app settings with an arm template (might be important for some buildout situations, to avoid having to use scripts in addition to arm templates):
Create the app settings as an outer child resource:
"resources": [
{
"type": "Microsoft.Web/sites",
"kind": "functionapp",
"name": "[parameters('name')]",
"apiVersion": "2016-03-01",
"location": "[parameters('location')]",
"properties": {
"name": "[parameters('name')]",
"clientAffinityEnabled": false,
"reserved": false
}
},
{
"name": "[concat(parameters('name'), '/', 'appsettings')]",
"type": "Microsoft.Web/sites/config",
"apiVersion": "2018-11-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites',parameters('name'))]"
],
"properties": {
"schedule1": "[parameters('schedule1')]",
"schedule2": "[parameters('schedule2')]"
}
}
]
That way it isn't detected as an override to the website and thus allowed to be deployed.

Resources