Arm templates copy loop conditionally check index - azure

Is there option copy iteration check conditionally in ARM templates? Example if copy index is zero set another value?
My ARM Code:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
},
"storageAccountName": {
"type": "string"
},
"mediaServicesAccountName": {
"type": "string"
}
},
"functions": [],
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-01-01",
"name": "[concat('storage', copyIndex(), uniqueString(resourceGroup().id))]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"copy": {
"name": "storagecopy",
"count": 3
}
},
{
"type": "Microsoft.Media/mediaservices",
"apiVersion": "2020-05-01",
"name": "[parameters('mediaServicesAccountName')]",
"location": "[parameters('location')]",
"properties": {
"storageAccounts": [
{
"type": "Primary", # Primary if copyIndex is zero otherwise Secondary
"id": "[resourceId('Microsoft.Storage/storageAccounts', concat('storage', copyIndex(), uniqueString(resourceGroup().id)))]"
}
]
},
"identity": {
"type": "SystemAssigned"
},
"dependsOn": ["storagecopy"]
}
],
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.3.126.58533",
"templateHash": "2006367938138350540"
}
}
}
In above code I am creating 3 storage accounts and after that I am creating azure media service, I need to map storage accounts to azure media service dynamically. Under properties, I need to use copy loop and set Primary if index is zero else Secondary for defined number of storages.
Below Block implementation is required for copy loop condition:
"storageAccounts": [
{
"type": "Primary", # Primary if copyIndex is zero otherwise Secondary
"id": "[resourceId('Microsoft.Storage/storageAccounts', concat('storage', copyIndex(), uniqueString(resourceGroup().id)))]"
}
]

You need to make a separate loop over storageAccounts property:
{
"type": "Microsoft.Media/mediaservices",
"apiVersion": "2020-05-01",
...
"properties": {
"copy": [
{
"name": "storageAccounts",
"count": "3",
"input": {
"type": "[if(equals(copyIndex('storageAccounts'), 0), 'Primary', 'Secondary']", # Primary if copyIndex is zero otherwise Secondary
"id": "[resourceId('Microsoft.Storage/storageAccounts', concat('storage', copyIndex('storageAccounts'), uniqueString(resourceGroup().id)))]"
}
}
]
}
}
See more information here: Property iteration in ARM templates

Related

Combining 2 ARM templates ( Action Group and Budget Alert)

I am having a bit of an issue in combining two ARM templates. I want to create an Action Group and then subsequently create an Azure budget alert. I am able to do them individually but I want to create a single Arm template to achieve the task.
Works Fine -Here is the code to create Action Group (Getting the Option to select Resource Group or create one)
{
"type": "microsoft.insights/actionGroups",
"apiVersion": "2019-06-01",
"name": "OG",
"location": "Global",
"properties": {
"groupShortName": "OG",
"enabled": true,
"emailReceivers": [
{
"name": "[concat('Test','OG')]",
"emailAddress": "someemailID#xxx.com",
"useCommonAlertSchema": true
}
]
}
Works Fine - Here is the code to create Budget Alert (Getting the option to select Subscription)
"resources": [
{
"type": "Microsoft.Consumption/budgets",
"apiVersion": "2019-10-01",
"name": "[parameters('budgetName')]",
"properties": {
"timePeriod": {
"startDate": "[parameters('startDate')]",
"endDate": "[parameters('endDate')]"
},
"timeGrain": "[parameters('timeGrain')]",
"amount": "[parameters('amount')]",
"category": "Cost",
"notifications": {
"NotificationForExceededBudget1": {
"enabled": true,
"operator": "GreaterThan",
"threshold": "[parameters('firstThreshold')]",
"contactEmails": "[parameters('contactEmails')]",
"contactGroups": "[parameters('contactGroups')]"
}
}
}
}
]
And here is the combined code that I have come up with - Not Getting the option to select Resource Group
Problem - While deploying the combined template I am not getting the option to select "Resource Group" I need to have "Resource Group" to create Action Group but not for Budget Alert.
How can I combine the two templates?
Not able to make it work
"resources": [
{
"type": "microsoft.insights/actionGroups",
"apiVersion": "2019-06-01",
"name": "OG",
"location": "Global",
"properties": {
"groupShortName": "OG",
"enabled": true,
"emailReceivers": [
{
"name": "[concat('Test','OG')]",
"emailAddress": "someemailID#xxx.com",
"useCommonAlertSchema": true
}
]
}
},
{
"type": "Microsoft.Consumption/budgets",
"apiVersion": "2019-10-01",
"name": "[parameters('budgetName')]",
"dependsOn": [
"[resourceId('microsoft.insights/actionGroups','OG')]"
],
"properties": {
"timePeriod": {
"startDate": "[parameters('startDate')]",
"endDate": "[parameters('endDate')]"
},
"timeGrain": "[parameters('timeGrain')]",
"amount": "[parameters('amount')]",
"category": "Cost",
"notifications": {
"NotificationForExceededBudget1": {
"enabled": true,
"operator": "GreaterThan",
"threshold": "[parameters('firstThreshold')]",
"contactEmails": "[parameters('contactEmails')]",
"contactGroups": "[resourceId('microsoft.insights/actionGroups','OG')]"
}
}
}
}
]
Please nested the budget in a nest template and target it to subscription level (or management group level)
https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/deploy-to-resource-group?tabs=azure-cli
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
your action group resource
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2020-06-01",
"name": "nestedDeployment",
"location": "centralus",
"subscriptionId": "0000000-0000-0000-0000-000000000000",
"properties": {
"mode": "Incremental",
"template": {
your budget resource goes here
}
}
}
],
"outputs": {}
}

Conditionally deploy a route table with subnet using ARM template

I am trying to conditionally deploy a route template when deploying a subnet resource using an ARM template, however, I am not able to do so using the if condition. Would anyone know the correct syntax or what am I doing wrong?
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"routeTableConfigurationObject": {
"type": "object",
"defaultValue": null
},
"vnetConfigurationObject": {
"type": "object"
},
"Tags": {
"type": "object"
}
},
"resources": [
{
"type": "Microsoft.Network/virtualNetworks/Subnets",
"name": "[concat(parameters('vnetConfigurationObject').name,'/',parameters('vnetConfigurationObject').subnets[copyIndex()].name)]",
"location": "[parameters('vnetConfigurationObject').location]",
"apiVersion": "2018-08-01",
"copy": {
"name": "vnet",
"count": "[length(parameters('vnetConfigurationObject').subnets)]",
"mode": "serial"
},
"properties": {
"addressPrefix": "[parameters('vnetConfigurationObject').subnets[copyIndex()].addressPrefix]",
"routeTable": "[if(empty('routeTableConfigurationObject'), json('null'), json(concat('{\"id\": \"', '/subscriptions/', parameters('routeTableConfigurationObject').subscriptionId, '/resourceGroups/', parameters('routeTableConfigurationObject').resourceGroupName, '/providers/Microsoft.Network/routeTables/', parameters('vnetConfigurationObject').subnets[copyIndex()].routeTable,'\"}')))]"
}
}
]
}
try this:
"variables": [
"copy": [
{
"name": "route",
"count": "[length(parameters('vnetConfigurationObject').subnets)]",
"input": {
"id": "[resourceId(parameters('routeTableConfigurationObject').subscriptionId, parameters('routeTableConfigurationObject').resourceGroupName, 'Microsoft.Network/routeTables', parameters('vnetConfigurationObject').subnets[copyIndex('route')].routeTable)]"
}
}
]
],
"resources": [
{
"type": "Microsoft.Network/virtualNetworks/Subnets",
"name": "[concat(parameters('vnetConfigurationObject').name,'/',parameters('vnetConfigurationObject').subnets[copyIndex()].name)]",
"location": "[parameters('vnetConfigurationObject').location]",
"apiVersion": "2018-08-01",
"copy": {
"name": "vnet",
"count": "[length(parameters('vnetConfigurationObject').subnets)]",
"mode": "serial"
},
"properties": {
"addressPrefix": "[parameters('vnetConfigurationObject').subnets[copyIndex()].addressPrefix]",
"routeTable": "[if(empty('routeTableConfigurationObject'), json('null'), variables('route')[copyIndex()])]"
}
}
]
I dont have time\ability to test it, but it should be pretty close

Using CopyIndex and listKeys in outputs section

I'm trying to get the primaryConnectionStrings from an aRM template that creates multiple notification hubs
But I get this error
Error: Code=InvalidTemplate; Message=Deployment template validation failed: 'The template output 'connectionStrings' at line '291' and column '30' is not valid: The
template function 'copyIndex' is not expected at this location. The function can only be used in a resource with copy specified. Please see https://aka.ms/arm-copy for usage details.. Please see
https://aka.ms/arm-template-expressions for usage details.'.
I am clearly missing what this actually means as I've tried various incarnations of the template all of which have a copy for the resource.
I've tried this with a nested template (apologies if i've mangled the template, just removed some extraneous items):
"resources": [
{
"type": "Microsoft.NotificationHubs/namespaces",
"apiVersion": "2017-04-01",
"name": "[parameters('notificationHubName')]",
"location": "[resourceGroup().location]",
"tags": {
"Environment": "[parameters('environment')]",
"DisplayName": "Notification Hub Namespace"
},
"sku": {
"name": "[parameters('notificationHubSku')]"
},
"kind": "NotificationHub",
"properties": {
"namespaceType": "NotificationHub"
}
},
{
"type": "Microsoft.NotificationHubs/namespaces/AuthorizationRules",
"apiVersion": "2017-04-01",
"name": "[concat(parameters('notificationHubName'), '/RootManageSharedAccessKey')]",
"tags": {
"Environment": "[parameters('environment')]",
"DisplayName": "Notification Hub Namespace Auth Rules"
},
"dependsOn": [
"[resourceId('Microsoft.NotificationHubs/namespaces', parameters('notificationHubName'))]"
],
"properties": {
"rights": [
"Listen",
"Manage",
"Send"
]
}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"name": "[concat('nestedTemplate', copyIndex('notificationHubEntities'))]",
"copy": {
"name": "notificationHubEntities",
"count": "[length(parameters('notificationHubEntities'))]"
},
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.NotificationHubs/namespaces/notificationHubs",
"apiVersion": "2017-04-01",
"name": "[concat(parameters('notificationHubName'), '/', parameters('notificationHubEntities')[copyIndex('notificationHubEntities')])]",
"location": "[resourceGroup().location]",
"tags": {
"Environment": "[parameters('environment')]",
"DisplayName": "Notification Hubs"
},
"dependsOn": [
"[resourceId('Microsoft.NotificationHubs/namespaces', parameters('notificationHubName'))]"
],
"properties": {
"authorizationRules": []
}
},
{
"type": "Microsoft.NotificationHubs/namespaces/notificationHubs/authorizationRules",
"apiVersion": "2017-04-01",
"name": "[concat(parameters('notificationHubName'), '/',parameters('notificationHubEntities')[copyIndex('notificationHubEntities')],'/DefaultFullSharedAccessSignature')]",
"tags": {
"Environment": "[parameters('environment')]",
"DisplayName": "Notification Hub Auth Rules"
},
"dependsOn": [
"[resourceId('Microsoft.NotificationHubs/namespaces/notificationHubs',parameters('notificationHubName'), parameters('notificationHubEntities')[copyIndex('notificationHubEntities')])]",
"[resourceId('Microsoft.NotificationHubs/namespaces', parameters('notificationHubName'))]"
],
"properties": {
"rights": [
"Listen",
"Manage",
"Send"
]
}
},
],
"outputs" : {
"connectionString" : {
"type" : "object",
"value": "[listKeys(resourceId('Microsoft.NotificationHubs/namespaces/NotificationHubs/AuthorizationRules',parameters('notificationHubName'), parameters('notificationHubEntities')[copyIndex('notificationHubEntities')], 'DefaultFullSharedAccessSignature'),'2016-03-01').primaryConnectionString]"
}
}
}
}
}
],
"outputs": {
"connectionStrings" :
{
"type": "array",
"value": "[reference(concat('nestedTemplate', copyIndex('notificationHubEntities'))).outputs.connectionString.value]"
}
}
}
I've also tried with this:
"resources": [
{
"type": "Microsoft.NotificationHubs/namespaces",
"apiVersion": "2017-04-01",
"name": "[parameters('notificationHubName')]",
"location": "[resourceGroup().location]",
"tags": {
"Environment": "[parameters('environment')]",
"DisplayName": "Notification Hub Namespace"
},
"sku": {
"name": "[parameters('notificationHubSku')]"
},
"kind": "NotificationHub",
"properties": {
"namespaceType": "NotificationHub"
}
},
{
"type": "Microsoft.NotificationHubs/namespaces/AuthorizationRules",
"apiVersion": "2017-04-01",
"name": "[concat(parameters('notificationHubName'), '/RootManageSharedAccessKey')]",
"tags": {
"Environment": "[parameters('environment')]",
"DisplayName": "Notification Hub Namespace Auth Rules"
},
"dependsOn": [
"[resourceId('Microsoft.NotificationHubs/namespaces', parameters('notificationHubName'))]"
],
"properties": {
"rights": [
"Listen",
"Manage",
"Send"
]
}
},
{
"type": "Microsoft.NotificationHubs/namespaces/notificationHubs",
"apiVersion": "2017-04-01",
"name": "[concat(parameters('notificationHubName'), '/', parameters('notificationHubEntities')[copyIndex()])]",
"location": "[resourceGroup().location]",
"tags": {
"Environment": "[parameters('environment')]",
"DisplayName": "Notification Hubs"
},
"copy": {
"name": "addNotificationHub",
"count": "[length(parameters('notificationHubEntities'))]"
},
"dependsOn": [
"[resourceId('Microsoft.NotificationHubs/namespaces', parameters('notificationHubName'))]"
],
"properties": {
"authorizationRules": []
}
},
{
"type": "Microsoft.NotificationHubs/namespaces/notificationHubs/authorizationRules",
"apiVersion": "2017-04-01",
"name": "[concat(parameters('notificationHubName'), '/',parameters('notificationHubEntities')[copyIndex()],'/DefaultFullSharedAccessSignature')]",
"copy": {
"name": "addNotificationHub",
"count": "[length(parameters('notificationHubEntities'))]"
},
"tags": {
"Environment": "[parameters('environment')]",
"DisplayName": "Notification Hub Auth Rules"
},
"dependsOn": [
"[resourceId('Microsoft.NotificationHubs/namespaces/notificationHubs',parameters('notificationHubName'), parameters('notificationHubEntities')[copyIndex()])]",
"[resourceId('Microsoft.NotificationHubs/namespaces', parameters('notificationHubName'))]"
],
"properties": {
"rights": [
"Listen",
"Manage",
"Send"
]
}
}
],
"outputs": {
"connectionStrings" :
{
"type": "array",
"value": "[listKeys(resourceId('Microsoft.NotificationHubs/namespaces/NotificationHubs/AuthorizationRules',parameters('notificationHubName'), parameters('notificationHubEntities')[copyIndex()], 'DefaultFullSharedAccessSignature'),'2016-03-01').primaryConnectionString]"
}
}
I've tried using object instead of array but to no avail, so I'm a bit confused, any help would be appreciated as the error message seems misleading to me or I'm just not interpreting it correctly.
To accomplish requirement of creating multiple notification hubs and it's authorization rules, you can use below ARM template.
Template Parameter File (notificationhub.parameters.json):
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"namespaceName": {
"value": "mm-namespace"
},
"notificationhubNamePrefix": {
"value": "mm-notificationhub"
},
"notificationhubAuthorizationruleNamePrefix": {
"value": "mm-notificationhubAuthorizationrule"
}
}
}
Template File (notificationhub.json):
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"namespaceName": {
"type": "string",
"defaultValue": "mm-namespace",
"metadata": {
"description": "namespaceName sample description"
}
},
"notificationhubNamePrefix": {
"type": "string",
"defaultValue": "mm-notificationhub",
"metadata": {
"description": "notificationhubName sample description"
}
},
"notificationhubAuthorizationruleNamePrefix": {
"type": "string",
"defaultValue": "mm-notificationhubAuthorizationrule",
"metadata": {
"description": "notificationhubAuthorizationruleName sample description"
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "The location in which the resources should be deployed."
}
},
"notificationhubNameSuffix": {
"type": "array",
"defaultValue": [
"00",
"01",
"02"
]
},
"notificationhubAuthorizationruleNameSuffix": {
"type": "array",
"defaultValue": [
"00",
"01",
"02"
]
}
},
"variables": {},
"resources": [
{
"name": "[parameters('namespaceName')]",
"type": "Microsoft.NotificationHubs/namespaces",
"apiVersion": "2017-04-01",
"location": "[parameters('location')]",
"tags": {},
"sku": {
"name": "Free"
},
"properties": {
"namespaceType": "NotificationHub"
}
},
{
"name": "[concat(parameters('namespaceName'), '/', parameters('notificationhubNamePrefix'), parameters('notificationhubNameSuffix')[copyIndex()])]",
"type": "Microsoft.NotificationHubs/namespaces/notificationHubs",
"apiVersion": "2017-04-01",
"location": "[parameters('location')]",
"sku": {
"name": "Free"
},
"copy": {
"name": "notificationhubscopy",
"count": "[length(parameters('notificationhubNameSuffix'))]"
},
"dependsOn": [
"[resourceId('Microsoft.NotificationHubs/namespaces', parameters('namespaceName'))]"
]
},
{
"name": "[concat(parameters('namespaceName'), '/', parameters('notificationhubNamePrefix'), parameters('notificationhubNameSuffix')[copyIndex()], '/', parameters('notificationhubAuthorizationruleNamePrefix'), parameters('notificationhubAuthorizationruleNameSuffix')[copyIndex()])]",
"type": "Microsoft.NotificationHubs/namespaces/notificationHubs/AuthorizationRules",
"apiVersion": "2017-04-01",
"properties": {
"rights": [
"Listen",
"Manage",
"Send"
]
},
"copy": {
"name": "notificationhubsauthroizationrulescopy",
"count": "[length(parameters('notificationhubAuthorizationruleNameSuffix'))]"
},
"dependsOn": [
"notificationhubscopy"
]
}
]
}
Deployment:
AFAIK, to accomplish requirement of getting output (in this case primaryConnectionStrings of multiple notification hubs' authorization rules) from ARM template is currently an unsupported feature. I already see related feature requests / feedback here and here. I would recommend you to up-vote these feature requests / feedback or create a new feature request / feedback explaining your use case and requirement. Azure feature team would consider and work on the feature request / feedback based on the votes, visibility and priority on it.
Azure document references:
ARM template reference for NotificationHubs
Resolve Invalid Template errors
Create multiple instances of a resource using copy and copyIndex
ARM template functions like list (ListKeys)
ARM template structure
Defining order for deploying resources in ARM templates
Hope this helps!! Cheers!!
You can't use a copy loop in outputs today - listing the keys is fine, but you have to know how many you need at design time and hardcode each output. We're working on a fix for that but not there yet.
You could emulate this by using your second option - deploying in a nested deployment and outputting each key in it's own deployment, but then you have to iterate through all the deployments to get all the outputs.

ARM - Add multiple VM to Recovery Services Vault (copyIndex)

I'm trying to use the Recovery Services where I can automatically add a VM to Azure Backup via ARM template. I have successfully done this on a single machine deploy, but I'm trying to import it for when multiple VMs are deployed.
Here is where I had help from:
https://www.francoisdelport.com/2017/03/automating-azure-vm-backups-using-arm-templates/
and
Azure ARM JSON template - Add VM to Recovery Services Vault in different Resource Group
Here is a snippet from a single deploy I had working
{
"apiVersion": "2017-05-10",
"name": "nestedTemplate",
"type": "Microsoft.Resources/deployments",
"resourceGroup": "Env1",
"dependsOn": [
"[concat('Microsoft.Compute/virtualMachines/', variables('vmName'))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"apiVersion": "2016-06-01",
"name": "[concat( parameters('recoveryVault'), '/Azure/', 'iaasvmcontainer;iaasvmcontainerv2;', parameters('vmRsg') , ';', parameters('vmPrefix'), '/vm;iaasvmcontainerv2;', parameters('vmRsg'),';', parameters('vmPrefix'))]",
"location": "[resourceGroup().location]",
"type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems",
"properties": {
"protectedItemType": "Microsoft.Compute/virtualMachines",
"policyId": "[resourceId('Microsoft.RecoveryServices/vaults/backupPolicies', parameters('recoveryVault'), parameters('recoveryPolicy'))]",
"sourceResourceId": "[resourceId(subscription().subscriptionId, parameters('vmRsg'), 'Microsoft.Compute/virtualMachines', parameters('vmPrefix'))]"
}
}
]
}
}
}
Now I'm trying to use that in a copyIndex form for VM deploy, and here is the code I've been testing with:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"adminUsername": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Username for the Virtual Machine."
}
},
"adminPassword": {
"type": "securestring",
"metadata": {
"description": "Password for the Virtual Machine."
}
},
"dnsNameForPublicIP": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Globally unique DNS Name for the Public IP used to access the Virtual Machine."
}
},
"windowsOSVersion": {
"type": "string",
"defaultValue": "2012-R2-Datacenter",
"allowedValues": [
"2008-R2-SP1",
"2012-Datacenter",
"2012-R2-Datacenter"
],
"metadata": {
"description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version. Allowed values: 2008-R2-SP1, 2012-Datacenter, 2012-R2-Datacenter."
}
},
"vmCount": {
"type": "int",
"defaultValue": 1
},
"virtualNetworkName": {
"type": "string"
},
"dataDiskCount": {
"type": "int",
"defaultValue": 1
},
"recoveryVault": {
"type": "string",
"metadata": {
"description": "Backup vault name"
}
},
"recoveryPolicy": {
"type": "string",
"metadata": {
"description": "Backcup policy name"
}
},
"vmPrefix": {
"type": "string",
"metadata": {
"description": "Prefix for VM names, used with vmCount to build the VM names"
}
},
"vmRsg": {
"type": "string",
"metadata": {
"description": "Resource group where VMs reside"
}
}
},
"variables": {
"imagePublisher": "MicrosoftWindowsServer",
"imageOffer": "WindowsServer",
"OSDiskName": "osdiskforwindowssimple",
"nicName": "myVMNic",
"subnetName": "Subnet",
"vhdStorageType": "Standard_LRS",
"publicIPAddressName": "myPublicIP",
"publicIPAddressType": "Dynamic",
"vhdStorageContainerName": "vhds",
"vmName": "MWindowsVM",
"vmSize": "Standard_A2",
"virtualNetworkName": "MyVNET",
"vnetId": "[resourceId(resourceGroup().name, 'Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]",
"subnetRef": "[concat(variables('vnetId'), '/subnets/', variables('subnetName'))]"
},
"resources": [
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[concat(variables('publicIPAddressName'), copyIndex(1))]",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "PublicIPAddress"
},
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[concat(parameters('dnsNameForPublicIP'), copyIndex(1))]"
}
},
"copy": {
"name": "publicIpCopy",
"count": "[parameters('vmCount')]"
}
},
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Network/networkInterfaces",
"name": "[concat(variables('nicName'), copyIndex(1))]",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "NetworkInterface"
},
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', concat(variables('publicIPAddressName'), copyIndex(1)))]"
],
"properties": {
"ipConfigurations": [
{
"name": "[concat('ipconfig', copyIndex(1))]",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', concat(variables('publicIPAddressName'), copyIndex(1)))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"copy": {
"name": "nicCopy",
"count": "[parameters('vmCount')]"
}
},
{
"apiVersion": "2017-03-30",
"copy": {
"name": "nodeCopy",
"count": "[parameters('vmCount')]"
},
"type": "Microsoft.Compute/virtualMachines",
"name": "[concat(variables('vmName'), copyIndex(1))]",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "VirtualMachine"
},
"dependsOn": [
"[resourceId('Microsoft.Network/networkInterfaces/', concat(variables('nicName'), copyIndex(1)))]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[variables('vmSize')]"
},
"osProfile": {
"computerName": "[concat(variables('vmName'), copyIndex(1))]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]"
},
"storageProfile": {
"imageReference": {
"publisher": "[variables('imagePublisher')]",
"offer": "[variables('imageOffer')]",
"sku": "[parameters('windowsOSVersion')]",
"version": "latest"
},
"osDisk": {
"createOption": "FromImage"
},
"copy": [
{
"name": "dataDisks",
"count": "[parameters('dataDiskCount')]",
"input": {
"diskSizeGB": 1023,
"lun": "[copyIndex('dataDisks')]",
"createOption": "Empty"
}
}
]
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', concat(variables('nicName'), copyIndex(1)))]"
}
]
}
}
},
{
"apiVersion": "2017-05-10",
"name": "nestedTemplate",
"type": "Microsoft.Resources/deployments",
"resourceGroup": "Env1",
"dependsOn": [
"[concat('Microsoft.Compute/virtualMachines/', concat(variables('vmName'), copyIndex(1)))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"copy": {
"name": "protectedItemsCopy",
"count": "[parameters('vmCount')]"
},
"apiVersion": "2017-03-30",
"name": "[concat( parameters('recoveryVault'), '/Azure/', 'iaasvmcontainer;iaasvmcontainerv2;', parameters('vmRsg') , ';', parameters('vmPrefix'), copyIndex(1), '/vm;iaasvmcontainerv2;', parameters('vmRsg'),';', parameters('vmPrefix'), copyIndex(1))]",
"location": "[resourceGroup().location]",
"type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems",
"properties": {
"protectedItemType": "Microsoft.Compute/virtualMachines",
"policyId": "[resourceId('Microsoft.RecoveryServices/vaults/backupPolicies', parameters('recoveryVault'), parameters('recoveryPolicy'))]",
"sourceResourceId": "[resourceId(subscription().subscriptionId ,parameters('vmRsg'),'Microsoft.Compute/virtualMachines', concat(parameters('vmPrefix'), copyIndex(1)) )]"
}
}
]
}
}
}
]
}
Sadly it reports an error when trying to deploy, which I can't figure out why because it seems to be correct.
Error: Code=InvalidTemplate; Message=Deployment template validation failed: 'The template resource 'nestedTemplate' at line '198' and column '10' is not valid: The template function 'copyIndex' is not expected at this location. The function can only be used in a resource with copy specified. Please see https://aka.ms/arm-copy for usage details.. Please see https://aka.ms/arm-template-expressions for usage details.'.
The deployment validation failed
FYI, line 198 is "name": "nestedTemplate",
Any ideas, please?
To expand upon #4c74356b41 answer I was missing the all important "index":{ "value": "[copyIndex()]" within "Microsoft.Resources/deployments" on the parent template.
For those wanting to know more, have a look at: https://learn.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-multiple
Ensure you have the ('index') parameter next to those items which need to be duplicated, such as "[concat(parameters('WHATEVER'), parameters('index'))]"
I also ended up having a nested source within my linked template for the overall design I was looking for.
So my parent template had a linked (child) template (to another file) with:
name": "[concat('nestings', copyIndex(1))]",
"type": "Microsoft.Resources/deployments", ...
My child template had all the usual buildings of a VM with the parameters ('index') to ensure the items which are duplicated are named correctly.
And finally at the bottom of the child template I had a nested template source so I could back the VM up to another resource group (had to be nested, otherwise you can't do multiple resource groups), which looked like this:
{
"apiVersion": "2017-05-10",
"name": "[concat('nestedTemplate', parameters('index'))]",
"type": "Microsoft.Resources/deployments",
"resourceGroup": "Env1",
"dependsOn": [
"[concat('Microsoft.Compute/virtualMachines/', concat(variables('vmName'), parameters('index')))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"apiVersion": "2016-06-01",
"name": "[concat( parameters('recoveryVault'), '/Azure/', 'iaasvmcontainer;iaasvmcontainerv2;', parameters('vmRsg') , ';', concat(parameters('vmPrefix'), parameters('index')), '/vm;iaasvmcontainerv2;', parameters('vmRsg'),';', concat(parameters('vmPrefix'), parameters('index')))]",
"location": "[resourceGroup().location]",
"type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems",
"properties": {
"protectedItemType": "Microsoft.Compute/virtualMachines",
"policyId": "[resourceId('Microsoft.RecoveryServices/vaults/backupPolicies', parameters('recoveryVault'), parameters('recoveryPolicy'))]",
"sourceResourceId": "[resourceId(subscription().subscriptionId, parameters('vmRsg'), 'Microsoft.Compute/virtualMachines', concat(parameters('vmPrefix'), parameters('index')))]"
}
}
]
}
}
}
So what its telling you that you are not supposed to use copyIndex() function in that place. Now why exactly this is happening I don't know, but I do know that inline templates are a mess (for instance they use parent template paremeters, not nested template), I'm pretty sure if you convert that template to a real nested template (so a linked template, completely separate file) the above syntax will work.
Also, I'm handling this in a separate manner. I'm using 1 single nested deployment for each VM I have, so I'm using copy on the deployment resource, not backup resource.

Azure RM Template. Deploy copy VM with unique secret from Key Vault

I would like to be able to create VMs amount of which I specify via parameters (achieved by copy) with different secret for each VM (ex. secret1 for VM1, secret2 for VM2, etc.) Here is a basic example of copy VM template:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"numberOfVMs": {
"type": "int",
"defaultValue": 1,
"minvalue": 1
},
"vmAdminUserName": {
"type": "string",
"minLength": 1
},
"vmAdminPassword": {
"type": "securestring"
}
},
"variables": {
"storageAccountName": "[concat('stor567', uniqueString(resourceGroup().id))]",
"storageAccountType": "Standard_LRS",
"vmWindowsOSVersion": "2016-Datacenter",
"vnetPrefix": "10.0.0.0/16",
"vnetSubnet1Name": "Subnet-1",
"vnetSubnet1Prefix": "10.0.0.0/24",
"nicVnetID": "[resourceId('Microsoft.Network/virtualNetworks', 'vnet')]",
"nicSubnetRef": "[concat(variables('nicVnetID'), '/subnets/', variables('vnetSubnet1Name'))]",
"vmImagePublisher": "MicrosoftWindowsServer",
"vmImageOffer": "WindowsServer",
"vmVmSize": "Standard_DS1_v2",
"vmVnetID": "[resourceId('Microsoft.Network/virtualNetworks', 'vnet')]",
"vmSubnetRef": "[concat(variables('vmVnetID'), '/subnets/', variables('vnetSubnet1Name'))]",
"vmStorageAccountContainerName": "vhds"
},
"resources": [
{
"name": "[variables('storageAccountName')]",
"type": "Microsoft.Storage/storageAccounts",
"location": "[resourceGroup().location]",
"apiVersion": "2015-06-15",
"dependsOn": [ ],
"properties": {
"accountType": "[variables('storageAccountType')]"
}
},
{
"name": "vnet",
"type": "Microsoft.Network/virtualNetworks",
"location": "[resourceGroup().location]",
"apiVersion": "2016-03-30",
"dependsOn": [ ],
"tags": {
"displayName": "vnet"
},
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('vnetPrefix')]"
]
},
"subnets": [
{
"name": "[variables('vnetSubnet1Name')]",
"properties": {
"addressPrefix": "[variables('vnetSubnet1Prefix')]"
}
}
]
}
},
{
"name": "[concat('NIC',copyindex())]",
"type": "Microsoft.Network/networkInterfaces",
"location": "[resourceGroup().location]",
"copy": {
"name": "nicLoop",
"count": "[parameters('numberOfVMs')]"
},
"apiVersion": "2016-03-30",
"dependsOn": [
"[resourceId('Microsoft.Network/virtualNetworks', 'vnet')]"
],
"tags": {
"displayName": "nic"
},
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "[variables('nicSubnetRef')]"
}
}
}
]
}
},
{
"name": "[concat('VM',copyindex())]",
"type": "Microsoft.Compute/virtualMachines",
"location": "[resourceGroup().location]",
"copy": {
"name": "virtualMachineLoop",
"count": "[parameters('numberOfVMs')]"
},
"apiVersion": "2015-06-15",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
"nicLoop"
],
"tags": {
"displayName": "vm"
},
"properties": {
"hardwareProfile": {
"vmSize": "[variables('vmVmSize')]"
},
"osProfile": {
"computerName": "[concat('VM',copyindex())]",
"adminUsername": "[parameters('vmAdminUsername')]",
"adminPassword": "[parameters('vmAdminPassword')]"
},
"storageProfile": {
"imageReference": {
"publisher": "[variables('vmImagePublisher')]",
"offer": "[variables('vmImageOffer')]",
"sku": "[variables('vmWindowsOSVersion')]",
"version": "latest"
},
"osDisk": {
"name": "vmOSDisk",
"vhd": {
"uri": "[concat(reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob, variables('vmStorageAccountContainerName'), '/', 'VM',copyIndex(),'-','OSdisk.vhd')]"
},
"caching": "ReadWrite",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', concat('NIC',copyindex()))]"
}
]
}
}
}],
"outputs": {}
}
However, I'm struggling to integrate using of password as unique secrets from Key Vault in that template. If I use example from official documentation Reference a secret with static id VMs with secret1 for each VM will be created. And I can’t wrap Reference a secret with dynamic id into nested template because that would deploy my copied VMs again and again for the each number of VMs I would like to deploy. Please help me understand, how this challenge can be solved?
Links: Parent and Nested.
I'm not sure if that's what you meant (because i still think that i struggle to understand your problem).
These templates allow to deploy variable amount of vm's and use different keyvault keys as passwords for those. Example:
2 Windows VM's with one secret and 3 Ubuntu VM's with another
1 Windows VM with one secret and 4 Ubuntu VM's with another
You can easily extend that to other images, like centos.
As you can probably see after looking at the templates I'm using arrays and copyindex() to feed proper values where they belong.
Tell me if that's not what you are after. Be careful when using those, github raw links use some form of caching, so deploying from github might not work for you with errors, in that case just use the links I've provided (NOT RAW) to copy to local machine and upload to some service like pastebin, and deploy from there.

Resources