Reputation: 21007
I'm setting up an ARM template for my Azure Functions. I have functions_app.json
which will function as the "main" template and will be used for other release pipelines as well.
Then there will be project specific parameter templates, like:
etc.
But I'm stuck at this part where it creates the actual Function resource inside the functions_app.json
template:
"resources": [
{
"apiVersion": "2018-11-01",
"name": "[variables('resourceName')]",
"type": "Microsoft.Web/sites",
"properties": {
"name": "[variables('resourceName')]",
"siteConfig": {
"appSettings": [
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~4"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "dotnet"
},
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('xxxx-xxxx','MyResourceGroup','Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('xxxx-xxxx','MyResourceGroup','Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[toLower(variables('resourceName'))]"
}
]
}
}
}
I left some things out for brevity. The import part is the appSettings
section.
My initial idea was to put that array
in variables
. Those will be the "default" settings. Then from my counter_function_arm.parameters.test.json
file I would add another array specific to that application and then union
those two together so all AppSettings
are configured.
Something like this:
counter_function_arm.parameters.test.json
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"customAppSettings":{
"value": [
{
"name": "CustomProperty",
"value": "some value"
}
]
}
}
}
functions_app.json
"variables": {
"defaultAppSettings": [
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~4"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "dotnet"
},
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('xxxx-xxxx','MyResourceGroup','Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('xxxx-xxxx','MyResourceGroup','Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[toLower(variables('resourceName'))]"
}
]
},
"resources": [
{
"apiVersion": "2018-11-01",
"name": "[variables('resourceName')]",
"type": "Microsoft.Web/sites",
"properties": {
"name": "[variables('resourceName')]",
"siteConfig": {
"appSettings": "[union(variables('defaultAppSettings'), parameters('customAppSettings'))]"
}
}
}
So in this case I union
the variable variables('defaultAppSettings')
and parameters('customAppSettings')
together.
But, the problem is that I can't use listKeys
in the variables
section. I also can't move this to the parameters
section and set it as defaultValue
because listKeys
is also not allowed there.
Adding a hard coded array to union
also doesn't work:
"appSettings": "[union([1,2,3], parameters('customAppSettings'))]"
So I am out of ideas.
Is there a way to do this?
Upvotes: 0
Views: 961
Reputation: 9
I took inspiration from James Hyot's answer and tweaked it a little to work with Azure DevOps Deployment.
This how I combined three arrays including WEBSITE_CONTENTAZUREFILECONNECTIONSTRING and AzureWebJobsStorage setting for a Function App.
"appSettings": "[union(
variables('defaultFunctionAppSettings'),
parameters('config').appSettings,
createArray(
createObject(
string('name'), string('AzureWebJobsStorage'),
string('value'), string(concat('DefaultEndpointsProtocol=https;AccountName=', parameters('funcStorageName'), ';AccountKey=', listKeys(resourceId('{resourceGroupId}', 'Microsoft.Storage/storageAccounts', parameters('funcStorageName')),'2022-09-01').keys[0].value))
)
),
createArray(
createObject(
string('name'), string('WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'),
string('value'), string(concat('DefaultEndpointsProtocol=https;AccountName=', parameters('funcStorageName'), ';AccountKey=', listKeys(resourceId('{resourceGroupId}', 'Microsoft.Storage/storageAccounts', parameters('funcStorageName')),'2022-09-01').keys[0].value))
)
)
)]"
Hope this can help someone out there.
Upvotes: 0
Reputation: 150
I've found a way around this, it is not pretty but it works... Basically I have an array of purely static app settings (in your case FUNCTIONS_EXTENSION_VERSION, FUNCTIONS_WORKER_RUNTIME, WEBSITE_CONTENTSHARE) that you can create in variables, an array of customAppSettings from parameters, and another array that I have to write as raw JSON. Here's an example :
"resources": [
{
"apiVersion": "2018-11-01",
"name": "[variables('resourceName')]",
"type": "Microsoft.Web/sites",
"properties": {
"name": "[variables('resourceName')]",
"siteConfig": {
"appSettings": "[union(variables('staticAppSettings'), parameters('customAppSettings'), json(concat('[{\"name\": \"AzureWebJobsStorage\",\"value\": \"', concat('DefaultEndpointsProtocol=https;AccountName=', parameters('azureFunctionParameters').functionStorageName,';AccountKey=', listKeys(resourceId(parameters('azureFunctionParameters').mainStorageResourceGroup, 'Microsoft.Storage/storageAccounts', parameters('azureFunctionParameters').functionStorageName), '2021-08-01').keys[1].value, ';EndpointSuffix=core.windows.net'),'\"}]')))]"
}
}
}
]
Sadly this is the best I could come up with, it is very prone to syntax error but if you're using multi-line strings you could format it in a way to avoid errors (but be careful if you're using Azure Devops it is not compatible).
EDIT : There is another way to accomplish this that may be more suitable for you, which is to use a linkedTemplate :
Main template
"resources": [
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2021-04-01",
"name": "yourResourceDeployment",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[concat(parameters('blobBaseUrl'), '/', parameters('resourceGroupName'), '/azurefunctions/baseazurefunction.json')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"azureFunctionParameters": {
"value": "[parameters('baseAzureFunctionParameters')]"
},
"appSettings": {
"value" : "[union(variables('staticAppSettings'), parameters('customAppSettings'))]"
},
"someMoreAppSettings": {
"value" : [
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('xxxx-xxxx','MyResourceGroup','Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('xxxx-xxxx','MyResourceGroup','Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]"
}
]
}
}
}
}
]
In the baseazurefunction.json linkedTemplate you can just use
{
"apiVersion": "2018-11-01",
"name": "[variables('resourceName')]",
"type": "Microsoft.Web/sites",
"properties": {
"name": "[variables('resourceName')]",
"siteConfig": {
"appSettings": "[union(parameters('appSettings'), parameters('moreAppSettings'))]"
}
}
}
If you have a security concern over sending connection strings as parameters to other templates, you could wrap your appsettings in a secureobject with a property called array which would contain all your settings and then use it like this instead
"appSettings": {
"type": "secureobject",
"metadata": {
"description": "Secure object with an array property which contains the appsettings"
}
},
"moreAppSettings": {
"type": "secureobject",
"metadata": {
"description": "Secure object with an array property which contains more appsettings"
}
}
.
.
.
"appSettings": "[union(parameters('appSettings').array, parameters('moreAppSettings').array)]"
Upvotes: 2