creating a new guid at each iteration in an ARM template - azure

I try to script an ARM template like this:
{
"type": "Microsoft.EventHub/namespaces/ipfilterrules",
"apiVersion": "2018-01-01-preview",
"name": "[concat(parameters('name'), '/fr', copyIndex())]",
"location": "[resourceGroup().location]",
"properties": {
"ipMask": "[parameters('ipMasks')[copyIndex()]]",
"action": "Accept",
"filterName": "[concat('fr', copyIndex())]"
},
"copy": {
"name": "eventHubfirewallrulescopy",
"count": "[length(parameters('ipMasks'))]"
}
What I really want to to is to feed "name" and "filtername" by the same GUID value. in order to have something like this:
{
"type": "Microsoft.EventHub/namespaces/ipfilterrules",
"apiVersion": "2018-01-01-preview",
"name": "[concat(parameters('name'), 'd8fb1ab2-a6cb-439f-9354-918bd96d6ace')]",
"location": "[resourceGroup().location]",
"properties": {
"ipMask": "[parameters('ipMasks')[copyIndex()]]",
"action": "Accept",
"filterName": "d8fb1ab2-a6cb-439f-9354-918bd96d6ace"
},
"copy": {
"name": "eventHubfirewallrulescopy",
"count": "[length(parameters('ipMasks'))]"
}
As you can see this is a multi deployement resource. So I need a new guid generated at each iteration.
I first thought to create a variable using guid() function, but it is not permitted.
Is there any other idea?
thank you

How about defining a proxy parameter in the template for which you do not pass the value and is initialed to default new guid and use it in the variables later to generate the unique string? Something like this
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"guidValue": {
"type": "string",
"defaultValue": "[newGuid()]"
}
},
"variables": {
"unique_string":"[uniqueString(parameters('guidValue'))]"
},
"resources":
[
.. your resources with names derived from variables('unique_string')
],
"outputs": {
"unique_string": {
"type": "string",
"value": "[variables('unique_string')]"
}
}
}
Hope this helps?

If the length of parameters('ipMasks') is known you can use either a pregenerated array of guids with the same length and then use copyIndex to pick the guid in the same way that you pick the ip masks. If a random guid is needed (in my case a static guid was needed) the same scenario with a generated guid list should work:
[
"[newGuid()]",
"[newGuid()]",
"[newGuid()]",
"[newGuid()]",
"[newGuid()]"
]
Assuming a fixed array parameter of 5 items.

Related

Azure arm output the IPs of an app service

I am using ARM template in azure to get the possibleOutboundIpAddresses from an app service and use the same to create allow firewall rule in mysql server
Below is the parameter I am using to fetch the ips
"parameters": {
"webAppOutboundIpAddresses": {
"value": "[split(reference(concat('Microsoft.Web/sites/',parameters('wpsitename'))).properties.possibleOutboundIpAddresses,',')]"
and below is the piece of code using in the linked template.
"type": "Microsoft.DBforMySQL/servers/firewallRules",
"apiVersion": "2017-12-01",
"name": "[concat(parameters('sqlServerName'), '/Allow WebApp Outbound IP ',copyIndex('webAppOutboundIPAddressesCopy'))]",
"properties": {
"startIpAddress": "[parameters('webAppOutboundIpAddresses')[copyIndex('webAppOutboundIPAddressesCopy')]]",
"endIpAddress": "[parameters('webAppOutboundIpAddresses')[copyIndex('webAppOutboundIPAddressesCopy')]]"
},
"copy": {
"name": "webAppOutboundIPAddressesCopy",
"count": "[length(parameters('webAppOutboundIpAddresses'))]"
But somehow the pipeline is failing and I am getting an invalid parameter error
InvalidParameterValue",
"message": "Invalid value given for parameter '{0}'. Specify a valid parameter value."
One more thing I noticed is the output IPs are showing inside square brackets like ["192.168.1.2"]
Can someone please shed some light on this? Thanks
If the resource is in the same template, you must use the reference() function and pass it the resource id or only the name:
{
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appServiceName": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Specifies the name of the Azure App Service"
}
},
"appServicePlanName": {
"type": "string",
"minLength": 1
}
},
"variables": {
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "[parameters('appServiceName')]",
"type": "Microsoft.Web/sites",
"kind": "app",
"location": "[resourceGroup().location]",
"dependsOn": [],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]",
"clientAffinityEnabled": false
},
"resources": [],
}
],
"outputs": {
"appServiceName": {
"type": "string",
"value": "reference(parameters('appServiceName'), '2016-03-01', 'Full').properties.inboundIpAddress"
},
"ipAddress": {
"type": "string",
"value": "whatingodsnamegoeshere"
}
}
}
OR
With resourced id:
reference(resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName')), '2016-03-01', 'Full').properties.inboundIpAddress
For the outbound ip addresses: refer here provided by AZToso.
"parameters": {
"webAppOutboundIpAddresses": {
"value": "[split(reference(concat('Microsoft.Web/sites/',variables('webAppName'))).possibleOutboundIpAddresses,',')]"
},
We must develop a loop to iterate through the list of potential outbound IP addresses for a Web App so that we can add each one to the SQL firewall. This is possible with ARM templates by utilizing the copy element. We must utilize the reference function to obtain the WebApp's object. However, the reference function has a restriction that prevents you from using it to change the value of the count property in a copy loop.

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.

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.

ARM Template concat resources

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.

Resources