I am trying to set up some tags within an ARM template in accordance with this article: https://learn.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-templates-resources#apply-an-object-to-the-tag-element
I wanted to be able to set up a couple of generic tags in the TagValues parameter, but then append others for specific resources. Is this possible, and if so how? I've tried using [concat()] but it's not happy dealing with objects, and fails validation.
Here's an example of what I'm trying to do:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"tagValues": {
"type": "object",
"defaultValue": {
"Dept": "Finance",
"Environment": "Production"
}
}
},
"resources": [
{
"apiVersion": "2016-01-01",
"type": "Microsoft.Storage/storageAccounts",
"name": "[concat('storage', uniqueString(resourceGroup().id))]",
"location": "[resourceGroup().location]",
"tags": "[parameters('tagValues')]", // want to concatenate another tag here, so that the following is returned: "Dept": "Finance", "Environment": "Production", "myExtraTag": "myTagValue"
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage",
"properties": {}
}
{
"apiVersion": "2016-01-01",
"type": "Microsoft.Storage/storageAccounts",
"name": "mySecondResource",
"location": "[resourceGroup().location]",
"tags": "[parameters('tagValues')]", // want to concatenate a DIFFERENT tag here, so that the following is returned: "Dept": "Finance", "Environment": "Production", "myExtraDifferentTag": "myDifferentTagValue"
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage",
"properties": {}
}
]
}
It's possible with union function. You can find more documentation about it here
Below solution might work for you. I have given 2 approaches. One with inline string converted to object with json function. Other approach is to create an object in variables and using union to concatenate both objects.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"tagValues": {
"type": "object",
"defaultValue": {
"Dept": "Finance",
"Environment": "Production"
}
}
},
"variables" : {
"customTag" : {"myExtraDifferentTag": "myDifferentTagValue", "myAnotherExtraDifferentTag": "myAnotherDifferentTagValue"}
},
"resources": [
{
"apiVersion": "2016-01-01",
"type": "Microsoft.Storage/storageAccounts",
"name": "[concat('storage', uniqueString(resourceGroup().id))]",
"location": "[resourceGroup().location]",
"tags": "[union(parameters('tagValues'),json('{\"myExtraTag\":\"myTagValue \"}'))]", //Concatenates `tagValues` object to inline object
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage",
"properties": {}
}
{
"apiVersion": "2016-01-01",
"type": "Microsoft.Storage/storageAccounts",
"name": "mySecondResource",
"location": "[resourceGroup().location]",
"tags": "[union(parameters('tagValues'),variables('customTag'))]", // Concatenates `tagValues` object to `customTag` object
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage",
"properties": {}
}
]
}
Good question Greg!
You can achieve what you are after with the below:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"variables": {
"testvar": "customtagfromvar"
},
"parameters": {
},
"resources": [
{
"apiVersion": "2016-01-01",
"type": "Microsoft.Storage/storageAccounts",
"name": "[concat('storage', uniqueString(resourceGroup().id))]",
"location": "[resourceGroup().location]",
"tags": {
"department": "Finance",
"customTag": "[concat(variables('testvar'), '-concatedtext')]"
},
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage",
"properties": {}
}
]
}
Hope this helps!
Related
I have an ARM template to deploy a sql.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
......
"resources": [
{
"type": "Microsoft.Sql/servers",
"apiVersion": "2021-05-01-preview",
"name": "[parameters('serverName')]",
"location": "[parameters('location')]",
"properties": {
"administratorLogin": "[parameters('administratorLogin')]",
"administratorLoginPassword": "[parameters('administratorLoginPassword')]"
}
},
{
"type": "Microsoft.Sql/servers/databases",
"apiVersion": "2021-05-01-preview",
"name": "[format('{0}/{1}', parameters('serverName'), parameters('sqlDBName'))]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard",
"tier": "Standard"
},
"dependsOn": [
"[resourceId('Microsoft.Sql/servers', parameters('serverName'))]"
]
}
]
}
When deployed it creates an empty sql database. How do we deploy the corresponding schema, tables, procedures?
We tested the below ARM template in our local environment it is working fine, Below statements are based on our analysis.
Here is the ARM template we have used to create a new SQL database, a table that is exported from the existing database.
Here is our template.json file:
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters":
{
"administratorLogin":
{
"type": "string"
},
"administratorLoginPassword":
{
"type": "securestring"
},
"databaseName":
{
"type": "string"
},
"location":
{
"type": "string"
},
"_artifactsLocationSasToken":
{
"type": "securestring"
}
},
"variables":
{
"collation": "SQL_Latin1_General_CP1_CI_AS",
"edition": "Premium",
"maxSizeBytes": "1073741824",
"requestedServiceObjectiveName": "P1",
"storageKeyType": "SharedAccessKey",
"version": "12.0",
"serverName": "[concat('sqldemo', uniqueString(resourceGroup().id))]"
},
"resources":
[
{
"name": "[variables('serverName')]",
"type": "Microsoft.Sql/servers",
"apiVersion": "2015-05-01-preview",
"location": "[parameters('location')]",
"properties":
{
"administratorLogin": "[parameters('administratorLogin')]",
"administratorLoginPassword": "[parameters('administratorLoginPassword')]",
"version": "[variables('version')]"
},
"resources":
[
{
"name": "AllowAllWindowsAzureIps",
"type": "firewallrules",
"apiVersion": "2015-05-01-preview",
"location": "[parameters('location')]",
"dependsOn":
[
"[concat('Microsoft.Sql/servers/', variables('serverName'))]"
],
"properties":
{
"endIpAddress": "0.0.0.0",
"startIpAddress": "0.0.0.0"
}
},
{
"name": "[parameters('databaseName')]",
"type": "databases",
"apiVersion": "2017-03-01-preview",
"location": "[parameters('location')]",
"dependsOn":
[
"[concat('Microsoft.Sql/servers/', variables('serverName'))]"
],
"properties":
{
"collation": "[variables('collation')]",
"edition": "[variables('edition')]",
"maxSizeBytes": "[variables('maxSizeBytes')]",
"requestedServiceObjectiveName": "[variables('requestedServiceObjectiveName')]"
},
"resources":
[
{
"name": "Import",
"type": "extensions",
"apiVersion": "2014-04-01-preview",
"dependsOn":
[
"[concat('Microsoft.Sql/servers/', variables('serverName'), '/databases/', parameters('databaseName'))]"
],
"properties":
{
"storageKeyType": "[variables('storageKeyType')]",
"storageKey": "[parameters('_artifactsLocationSasToken')]",
"storageUri": "<Storageuri_of_exported_bacpacfile>", ##pass the bloburi of the bacpac file
"administratorLogin": "[parameters('administratorLogin')]",
"administratorLoginPassword": "[parameters('administratorLoginPassword')]",
"operationMode": "Import"
}
}
]
}
]
}
],
"outputs":
{
"serverName":
{
"type": "object",
"value": "[reference(variables('serverName'))]"
}
}
}
Here is the template.parameters.json file :
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters":
{
"administratorLogin":
{
"value": "<sqlserverusername>"
},
"databaseName":
{
"value": "<dbtablename>"
},
"location":
{
"value": "<location>"
},
"_artifactsLocationSasToken":{
"value": "<SAS token of the exported bacpacfile>"
}
}
}
Here is the sample output for reference:
Note:
You need to create a firewall rule with your Public IP in the SQL database post-creating sql DB with a table using the above ARM template.
I am trying to create multiple storage accounts with arm templates, However i am unable to find syntax for creating with name as parameters. below is the my template.json file
Template.json:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-
01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageName": {
"type": "array",
"metadata": {
"description": "storageaccountname"
}
}
},
"functions": [],
"variables": {},
"resources": [{
"name": "[concat('storage', uniqueString(resourceGroup().id),
parameters('storageName')[copyIndex('copystorage')])]",
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"tags": {
"displayName": "storageaccount1"
},
"location": "[resourceGroup().location]",
"kind": "StorageV2",
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"copy": {
"name": "copystorage",
"count": "[length(parameters('storageName'))]"
}
}],
"outputs": {}
}
Parameters.json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-
01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageName": {
"value": [
"storarm1",
"storearm2",
"storearm3"
]
}
}
}
so what should be the "name" property inorder to create storage accounts with
storearm1,storearm2 and storearm3
after modifying from
"name": "[concat('storage', uniqueString(resourceGroup().id), parameters('storageName')[copyIndex('copystorage')])]", to
"name": "[ parameters('storageName')[copyIndex('copystorage')]]",
i able to create with the names
I am creating an ARM template that deploys a lot of resources in different resource groups. Actually, the resource groups themselves are part of the deployment. In a simple version, I create only two resource groups (masterRG and dependentRG), and then create two nested (inline) deployments. The first inline deployment puts a storage account (testsadj1604) in masterRG. This deployment is dependent on masterRG.
The second deployment creates a keyvault and tries to store a connectionstring from testsadj1604 in that vault.
In my real case, I have more resourceGroups and I actually try deploy a Function App that has the connectionstring as 'appsetting'. The methodology is the same though.
The error I get is the following:
Deployment failed. Correlation ID: 9c359e8e-8657-4756-a5a3-f9c5698fbb46. {
"error": {
"code": "ResourceNotFound",
"message": "The Resource 'Microsoft.Storage/storageAccounts/testsadj1604' under resource group '<null>' was not found."
}
}
This is my code:
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"name": "masterRG",
"location": "West Europe",
"properties": {}
},
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"name": "dependentRG",
"location": "West Europe",
"properties": {}
},
{
"name": "masterRgDeployment",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"resourceGroup": "masterRG",
"dependsOn": [
"masterRG"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"apiVersion": "2019-06-01",
"type": "Microsoft.Storage/storageAccounts",
"name": "testsadj1604",
"location": "West Europe",
"sku": {
"name": "Standard_GRS"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true
}
}
]
}
}
},
{
"name": "dependentRgDeployment",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"resourceGroup": "dependentRG",
"dependsOn": [
"dependentRG",
"masterRgDeployment"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.KeyVault/vaults",
"name": "kvaNameTest1604",
"apiVersion": "2015-06-01",
"location": "West Europe",
"properties": {
"enabledForDeployment": false,
"enabledForDiskEncryption": false,
"enabledForTemplateDeployment": true,
"tenantId": "[subscription().tenantId]",
"accessPolicies": [
{
"objectId": "fc05639d-70eb-4175-a89b-eab7f883c691",
"tenantId": "[subscription().tenantId]",
"permissions": {
"keys": [
"get",
"list",
"update"
],
"secrets": [
"get",
"list",
"update"
]
}
}
],
"sku": {
"name": "Standard",
"family": "A"
},
"networkAcls": {
"defaultAction": "Allow",
"bypass": "AzureServices"
}
}
},
{
"type": "Microsoft.KeyVault/vaults/secrets",
"name": "kvaNameTest1604/saConnectionString",
"apiVersion": "2018-02-14",
"location": "West Europe",
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', 'kvaNameTest1604')]"
],
"properties": {
"value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', 'testsadj1604'), '2019-06-01').keys[0].value]"
}
}
]
}
}
}
]
}
I am pretty sure the error comes from the listKeys() at the bottom, going on my daylong trial-and-error (and Google frenzy) in my real template. Using listKeys() and nested deployments is a drag, but I really don't see why it shouldn't work. I made sure there is a dependsOn in the second deployment.
This is wrecking my brain, is there any way I can use nested (inline) templates and reference the storage account keys that are in a different resourcegroup (but part of the overall deployment)? I also tried to create an output in the first deployment and reference that in the second but that had no effect. I am at a total loss, any help is welcome!
There are a few things you need to do for this to work in a single template:
on your keyvault secret deployment set this property:
"expressionEvaluationOptions": {
"scope": "inner"
},
That will delay evaluation of the expression until that deployment begins.
when you set #1, you need to define parameters for the values you need (you can no longer use "global" params/vars), you could hardcode all the strings like in your example, but I'm guessing that's not what you do in the "real" deployment. Define the params and pass the values into that deployment.
your listKeys() call needs to include the full resourceId of the storageAccount, since it is in a separate/distinct deployment - so you need to provide the resourceGroup name param - if you cross subscriptions in the deployment (your sample didn't) you need to provide the subscriptionId param.
Below is a working example...
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"defaultValue": "[concat('scratch', uniqueString(newGuid()))]"
}
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"name": "masterRG",
"location": "West Europe",
"properties": {
}
},
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"name": "dependentRG",
"location": "West Europe",
"properties": {
}
},
{
"name": "masterRgDeployment",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"resourceGroup": "masterRG",
"dependsOn": [
"masterRG"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"apiVersion": "2019-06-01",
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('storageAccountName')]",
"location": "West Europe",
"sku": {
"name": "Standard_GRS"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true
}
}
]
}
}
},
{
"name": "dependentRgDeployment",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"resourceGroup": "dependentRG",
"dependsOn": [
"dependentRG",
"masterRgDeployment"
],
"properties": {
"mode": "Incremental",
"expressionEvaluationOptions": {
"scope": "inner"
},
"parameters":{
"storageAccountName": {
"value": "[parameters('storageAccountName')]"
},
"storageAccountResourceGroupName": {
"value": "masterRG"
}
},
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string"
},
"storageAccountResourceGroupName": {
"type": "string"
}
},
"variables": {
"vaultName": "[concat('kv-', parameters('storageAccountName'))]"
},
"resources": [
{
"type": "Microsoft.KeyVault/vaults",
"name": "[variables('vaultName')]",
"apiVersion": "2019-09-01",
"location": "West Europe",
"properties": {
"enabledForDeployment": false,
"enabledForDiskEncryption": false,
"enabledForTemplateDeployment": true,
"tenantId": "[subscription().tenantId]",
"accessPolicies": [ ],
"sku": {
"name": "Standard",
"family": "A"
},
"networkAcls": {
"defaultAction": "Allow",
"bypass": "AzureServices"
}
}
},
{
"type": "Microsoft.KeyVault/vaults/secrets",
"name": "[concat(variables('vaultName'), '/saConnectionString')]",
"apiVersion": "2019-09-01",
"location": "West Europe",
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', variables('vaultName'))]"
],
"properties": {
"value": "[listKeys(resourceId(parameters('storageAccountResourceGroupName'),'Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2019-06-01').keys[0].value]"
}
}
]
}
}
}
]
}
I am creating NSGs with copy function and it works fine. However, I would like to apply the NSGs similar way to the subnets but I get the copyindex not expected.
{
"apiVersion": "2017-08-01",
"name": "apply-nsg-to-subnet",
"type": "Microsoft.Resources/deployments",
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "#{BuildNumber}#",
"resources": [
{
"apiVersion": "2018-06-01",
"type": "Microsoft.Network/virtualNetworks/subnets",
"name": "[concat(parameters('vnetName') , '/' , parameters('subnets').settings[copyIndex()].name)]",
"location": "[variables('location')]",
"copy":{
"name": "subnetLoop",
"count": "[variables('subnetcount')]",
"mode": "Serial"
},
"properties": {
"addressPrefix": "[parameters('subnets').settings[copyIndex()].addressPrefix]",
"networkSecurityGroup": {
"id": "[resourceId('Microsoft.Network/networkSecurityGroups', concat(parameters('nsgNameAffix'), parameters('subnets').settings[copyIndex()].name, variables('nsgNameSuffix')))]"
}
}
}
]
}
}
}
What is wrong with my copyIndex use and how it should be used in this case?
This is due to the nested inline template you are using, I was able to repro and I was able to work around with this sample template:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"subnets": {
"type": "array",
"defaultValue": [
{
"name": "testo",
"addressPrefix": "10.0.0.0/24"
},
{
"name": "testo1",
"addressPrefix": "10.0.1.0/24"
}
]
}
},
"resources": [
{
"apiVersion": "2018-08-01",
"name": "vnet-testo",
"type": "Microsoft.Network/virtualNetworks",
"location": "[resourceGroup().location]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"10.0.0.0/16"
]
}
}
},
{
"apiVersion": "2015-06-15",
"type": "Microsoft.Network/networkSecurityGroups",
"name": "[parameters('subnets')[copyIndex()].name]",
"location": "[resourceGroup().location]",
"copy": {
"name": "nsg",
"count": "[length(parameters('subnets'))]"
},
"properties": {
"securityRules": []
}
},
{
"name": "NestedDeployment1",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2015-01-01",
"dependsOn": [
"nsg",
"vnet-testo"
],
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "https://paste.ee/d/iCWEu/0",
"contentVersion": "1.0.0.0"
},
"parameters": {
"subnets": {
"value": "[parameters('subnets')]"
}
}
}
}
]
}
Basically, I've just converted your template to a nested template (not inline).
ps. checkout my copy definition, its a bit better then yours.
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.