Well you can easy provide Azure App Service App setting in ARM template. Right? But what if you would like to merge settings included in script with those provided outside. This is another story.
Look at the script below. The sample above shows a ARM template to deploy Azure Function App.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"functionStorageName": {
"type": "string",
"metadata": {
"description": "Name of the storage."
}
},
"functionName": {
"type": "string",
"metadata": {
"description": "Name of the function."
}
},
"functionAppServicePlanName":{
"type": "string",
"metadata": {
"description": "Name of the app service plan."
}
},
"functionPackageUri": {
"type": "string",
"metadata": {
"description": "Uri to function package."
}
}
},
"variables":
{
"functionStorageAccountId": "[concat(resourceGroup().id,'/providers/','Microsoft.Storage/storageAccounts/', parameters('functionStorageName'))]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('functionStorageName')]",
"apiVersion": "2019-04-01",
"location": "[resourceGroup().location]",
"kind": "Storage",
"sku": {
"name": "Standard_LRS"
},
"properties": {
"supportsHttpsTrafficOnly": true
}
},
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2019-08-01",
"name": "[parameters('functionAppServicePlanName')]",
"location": "[resourceGroup().location]",
"sku": {
"name": "Y1",
"tier": "Dynamic"
},
"properties": {
"name": "[parameters('functionAppServicePlanName')]",
"computeMode": "Dynamic"
}
},
{
"apiVersion": "2019-08-01",
"type": "Microsoft.Web/sites",
"name": "[parameters('functionName')]",
"location": "[resourceGroup().location]",
"identity": {
"type": "SystemAssigned"
},
"kind": "functionapp",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', parameters('functionAppServicePlanName'))]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageName'))]"
],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('functionAppServicePlanName'))]",
"siteConfig": {
"appSettings": [
{
"name": "AzureWebJobsDashboard",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('functionStorageName'), ';AccountKey=', listKeys(variables('functionStorageAccountId'),'2015-05-01-preview').key1)]"
},
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('functionStorageName'), ';AccountKey=', listKeys(variables('functionStorageAccountId'),'2015-05-01-preview').key1)]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('functionStorageName'), ';AccountKey=', listKeys(variables('functionStorageAccountId'),'2015-05-01-preview').key1)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[toLower(parameters('functionName'))]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~2"
},
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "8.11.1"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "dotnet"
}
]
}
},
"resources": [
{
"name": "MSDeploy",
"type": "extensions",
"location": "[resourceGroup().location]",
"apiVersion": "2015-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('functionName'))]"
],
"properties": {
"packageUri": "[concat(parameters('functionPackageUri'))]"
}
}
]
}
]
}
If you look in the code you’ll see there are some static configuration values like FUNCTIONS_WORKER_RUNTIME
and other. There are also some dynamic configuration values which are only available during the deployment like the account storage key provided by listkeys
function inside of AzureWebJobsDashboard
variable. You can always add your own configuration values to those from outside of the script over custom parameters. No magic here. See customConfigurationValue
parameter below which is linked CustomConfigurationKey
in appSettings
array.
"customConfigurationValue":
{
"type": "string",
"metadata": {
"description": "Custom configuration value for function app settings."
}
...
"appSettings":
[
{
"name": "CustomConfigurationKey",
"value": "[parameters('customConfigurationValue')]"
}
]
Well, things are going to be more tricky when you need provide some multiple parameters outside of the script. Sure you can always add new parameter value, but this isn’t very nice. You will always need to make a change in script, if so.
What if we could do something like this?
"functionConfiguration":
{
"value": [
{
"name": "CustomConfigurationKey1",
"value": "customConfigurationValue1"
},
{
"name": "CustomConfigurationKey2",
"value": "customConfigurationValue2"
}
]
}
You see, there isn’t configuration parameter for each value, instead I use array
of values. And now things are going to be complicated and ugly. Not because of array
, you can use array
inside of appSettings
naturally. Because now we need to merge dynamic values I described before with those provided over parameter functionConfiguration
in the code above.
"siteConfig": {
"appSettings": "[union(parameters('functionConfiguration'),
createArray(
json(concat('{\"name\": \"AzureWebJobsDashboard\", \"value\": \"',concat('DefaultEndpointsProtocol=https;AccountName=', parameters('functionStorageName'), ';AccountKey=', listKeys(variables('functionStorageAccountId'),'2019-06-01').keys[0].value),'\"}')),
json(concat('{\"name\": \"AzureWebJobsDashboard\", \"value\": \"',concat('DefaultEndpointsProtocol=https;AccountName=', parameters('functionStorageName'), ';AccountKey=', listKeys(variables('functionStorageAccountId'),'2019-06-01').keys[0].value),'\"}')),
json(concat('{\"name\": \"AzureWebJobsStorage\", \"value\": \"',concat('DefaultEndpointsProtocol=https;AccountName=', parameters('functionStorageName'), ';AccountKey=', listKeys(variables('functionStorageAccountId'),'2019-06-01').keys[0].value),'\"}')),
json(concat('{\"name\": \"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING\", \"value\": \"',concat('DefaultEndpointsProtocol=https;AccountName=', parameters('functionStorageName'), ';AccountKey=', listKeys(variables('functionStorageAccountId'),'2019-06-01').keys[0].value),'\"}')),
json(concat('{\"name\": \"WEBSITE_CONTENTSHARE\", \"value\": \"',parameters('functionStorageName'),'\"}'))))]"
}
What I use ist the union
function to merge arrays together. The ugly part: well you can see it, I need to create for each value json and literally stick it together with concat
function. Maybe there ist more beautiful way I didn’t find anything else. It wouldn’t be that ugly if ARM templates would support expressions inside of strings which isn’t that case now.
Hope it helps some of you. See completed script below.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"functionStorageName": {
"type": "string",
"metadata": {
"description": "Name of the storage."
}
},
"functionName": {
"type": "string",
"metadata": {
"description": "Name of the function."
}
},
"functionAppServicePlanName":{
"type": "string",
"metadata": {
"description": "Name of the app service plan."
}
},
"functionPackageUri": {
"type": "string",
"metadata": {
"description": "Uri to function package."
}
},
"functionConfiguration":{
"type": "array",
"metadata": {
"description": "Configuration entries for function."
}
}
},
"variables":
{
"functionStorageAccountId": "[concat(resourceGroup().id,'/providers/','Microsoft.Storage/storageAccounts/', parameters('functionStorageName'))]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('functionStorageName')]",
"apiVersion": "2019-04-01",
"location": "[resourceGroup().location]",
"kind": "Storage",
"sku": {
"name": "Standard_LRS"
},
"properties": {
"supportsHttpsTrafficOnly": true
}
},
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2019-08-01",
"name": "[parameters('functionAppServicePlanName')]",
"location": "[resourceGroup().location]",
"sku": {
"name": "Y1",
"tier": "Dynamic"
},
"properties": {
"name": "[parameters('functionAppServicePlanName')]",
"computeMode": "Dynamic"
}
},
{
"apiVersion": "2019-08-01",
"type": "Microsoft.Web/sites",
"name": "[parameters('functionName')]",
"location": "[resourceGroup().location]",
"identity": {
"type": "SystemAssigned"
},
"kind": "functionapp",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', parameters('functionAppServicePlanName'))]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageName'))]"
],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('functionAppServicePlanName'))]",
"siteConfig": {
"appSettings": "[union(parameters('functionConfiguration'),
createArray(
json(concat('{\"name\": \"AzureWebJobsDashboard\", \"value\": \"',concat('DefaultEndpointsProtocol=https;AccountName=', parameters('functionStorageName'), ';AccountKey=', listKeys(variables('functionStorageAccountId'),'2019-06-01').keys[0].value),'\"}')),
json(concat('{\"name\": \"AzureWebJobsDashboard\", \"value\": \"',concat('DefaultEndpointsProtocol=https;AccountName=', parameters('functionStorageName'), ';AccountKey=', listKeys(variables('functionStorageAccountId'),'2019-06-01').keys[0].value),'\"}')),
json(concat('{\"name\": \"AzureWebJobsStorage\", \"value\": \"',concat('DefaultEndpointsProtocol=https;AccountName=', parameters('functionStorageName'), ';AccountKey=', listKeys(variables('functionStorageAccountId'),'2019-06-01').keys[0].value),'\"}')),
json(concat('{\"name\": \"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING\", \"value\": \"',concat('DefaultEndpointsProtocol=https;AccountName=', parameters('functionStorageName'), ';AccountKey=', listKeys(variables('functionStorageAccountId'),'2019-06-01').keys[0].value),'\"}')),
json(concat('{\"name\": \"WEBSITE_CONTENTSHARE\", \"value\": \"',parameters('functionStorageName'),'\"}'))))]"
}
},
"resources": [
{
"name": "MSDeploy",
"type": "extensions",
"location": "[resourceGroup().location]",
"apiVersion": "2015-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('functionName'))]"
],
"properties": {
"packageUri": "[concat(parameters('functionPackageUri'))]"
}
}
]
}
]
}
Oh one thing. You are not allowed to use new line inside of arm template, so the whole union
thing need to be at one line.
Thank you for the write up. I was struggling to understand why when I deployed my appsettings after the function app it was overwriting the appsettings within the function app. Changing to this structure resolved my problem.
LikeLiked by 1 person
Hello,
actually what you are suggesting is not working. I am trying to merge an array of appsettings taken from a parameter file like your example with a key taken from reference of managed identity resource just deployed before the appservice. It gives validation error that doesn’t have any connection with that part.
Here my code:
[union(parameters(‘siteCollaborationParameters’).siteConfig.appSettings,createObject(json(concat(‘{\”KeyVaultConfiguration__ClientId\”:\”‘, reference(resourceId(‘Microsoft.ManagedIdentity/userAssignedIdentities’,parameters(‘managedUserIdentity’).Name),’2018-11-30′,’Full’).properties.clientId, ‘\”}’))))]
LikeLike