ARM Template concat resources - azure

I have in ARM template:
"parameters": {
"applications": {
"value": "app1|app2|...|app(n)"
}
},
"variables": {
"applications": "[split(parameters('applications'), '|')]"
},
{
"name": "[concat('notificationhub', copyIndex())]",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2016-09-01",
"dependsOn": [
"[concat('Microsoft.NotificationHubs/namespaces/',variables('notificationHubNamespace'))]"
],
"copy": {
"name": "notificationhubCopy",
"count": "[length(variables('applications'))]"
},
"parameters": {
"notificationHubNamespace": { "value" : "variables('notificationHubNamespace')]" },
"notificationHubName": { "value": "[concat('notificationhub-', variables('applications')[copyIndex()])]" },
...
}
}
},
How to concat created notificationhub1 and notificationhub2 into one value in app settings like
"notificationhub1.connection|notificationhub2.connection|...|notificationhub(n).connection"
or is there an option to dynamically create in app settings based on count properties with respective values?
{
"name": "[variables('webappName')]",
"type": "Microsoft.Web/sites",
"location": "[resourceGroup().location]",
"resources": [
{
"name": "appsettings",
"type": "config",
"properties": {
"MobileApps": "[parameters('applications')]",
"NotificationHubs": "???",
-- OR --
"App1NotificationHub": "notificationhub1.connection"
"App2NotificationHub": "notificationhub2.connection"
"App(n)NotificationHub": "notificationhubn(n).connection"
}
}
},

I can't be 100% sure on this, but looking at what you have here something like this should work:
[concat(listKeys(resourceId('Microsoft.EventHub/namespaces/authoriz‌​ationRules', 'eventHubNamespaceName', 'keyName'),'2015-08-01').primaryConnectionString, '|', listKeys(resourceId('Microsoft.EventHub/namespaces/authoriz‌​ationRules', 'eventHubNamespaceName', 'keyName'),'2015-08-01').primaryConnectionString)]
I can't verify if listkeys for eventhubs do work like this, but when you figure out how listkeys works, you should be able to paste that into the example above.
Also '|' might need escaping. I suppose escaping is done with \.
edit: Again, I'm not sure about this, I've never tried it, but you would want to use this link and try to reproduce it with notification hubs instead of storage accounts.

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.

Configure FirewallRules for all PossibleOutboundIpAddresses in ARM Template

I would like to create firewall rules so that only my Azure Web App can connect to my database. If possible, I'd like to do this in my ARM template. Here's what I have tried so far:
{
"variables": {
"defaultResourceName": "[resourceGroup().name]",
},
"resources": [
{
"type": "Microsoft.Web/sites/firewallRules",
"name": "[concat('AllowAzureIpAddress', copyIndex()",
"apiVersion": "2015-05-01-preview",
"properties": {
"startIpAddress": "[reference('Microsoft.Web/sites', variables('defaultResourceName')).possibleOutboundIpAddresses[copyIndex()]]",
"endIpAddress": "[reference('Microsoft.Web/sites', variables('defaultResourceName')).possibleOutboundIpAddresses[copyIndex()]]"
},
"dependsOn": [
"[resourceId('Microsoft.Sql/servers/', toLower(variables('defaultResourceName')))]"
],
"copy": {
"name": "firewallRuleCopy",
"count": "[length(reference('Microsoft.Web/sites', variables('defaultResourceName')).possibleOutboundIpAddresses)]"
}
},
]
}
The main problem is getting the PossibleOutboundIpAddresses. I'm not sure if they're available to me here, and I'm getting an error when I try to validate my ARM Template that says The template function 'reference' is not expected at this location. Please see https://aka.ms/arm-template-expressions for usage details..
Has anyone done this that has any advice for how to go about getting those OutboundIpAddresses (preferably in a list so that copy can use them)?
your problem comes not from using reference function in a wrong fashion, but from the fact that you cant use reference function in copy property (copy is evaluated at "compile time" whereas reference at runtime, so it cannot evaluate length of the copy). your possible work around is: nested deployment. here's what I've been using:
{
"name": "firewallRules",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2015-01-01",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "https://paste.ee/d/Hkebg/0",
"contentVersion": "1.0.0.0"
},
"parameters": {
"prefix": {
"value": "[variables('prefix')]"
},
"iterator": {
"value": "[split(reference(concat(parameters('prefix'), '-', parameters('webAppNames').name), '2016-03-01', 'Full').properties.possibleOutboundIpAddresses, ',')]"
}
}
}
},

Azure availability zone parameter syntax

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

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