How to use copyIndex in a nested template resource? - azure

Big picture: I want to use the ARM template to create multiple topics on a service bus.
Known fact: The app service that deploys the template is in a different resource group than the service bus.
Pain point: I'm using a nested template because I'm trying to create resources (topics) that are external to the targeted resource group. Within this nested template, I'm not sure how to get copy to work correctly.
From this MS doc , I believe my syntax is correct.
This is how my parameters are listed:
"sharedResourcesResourceGroupName": {
"type": "string",
"defaultValue": "sharedResourceGroupName",
"metadata": {
"description": "Resource Group in which platform shared resources live"
}
},
"serviceBusNamespaceName": {
"type": "string",
"defaultValue": "serviceBusName",
"metadata": {
"description": "Name of the Service Bus namespace"
}
},
"topics": {
"type": "array",
"metadata": {
"description": "List of topics"
},
"defaultValue": [
"topic1",
"topic2"
]
}
This is my resource object for creating the topics with the copyIndex() method:
{
"apiVersion": "2018-05-01",
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('serviceBusNamespaceName'))]",
"resourceGroup": "[parameters('sharedResourcesResourceGroupName')]",
"properties": {
"mode": "Incremental",
"template":{
"$schema": "2018-05-01",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"type": "Microsoft.ServiceBus/namespaces/topics",
"name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('topics')[copyIndex()])]",
"apiVersion": "2017-04-01",
"location": "[resourceGroup().location]",
"properties": {},
"copy": {
"name": "topics",
"count": "[length(parameters('topics'))]"
},
"dependsOn": [
"[parameters('serviceBusNamespaceName')]"
]
}
]
}
}
}
I am testing the arm template deployment using the Azure Powershell with these commands:
Connect-AzAccount
Set-AZContext -SubscriptionName subscriptionWhereTheAppServiceLives
New-AzResourceGroupDeployment -ResourceGroupName resourceGroupWhereAppServiceLives -TemplateFile <path to template file>\azuredeploy.json -TemplateParameterFile <path to parameters file>\azuredeploy.parameters.json
The error I'm getting from the Azure powershell console is:
The template function 'copyIndex' is not expected at this location. The function can only be used in a resource with copy specified.
If I remove the "copy" object and replace "name" with something like "[concat(parameters('serviceBusNamespaceName'), '/topicName')]", then the template is able to create ONE topic in the right service bus. But I'm looking to create multiple topics.
Any insight would be greatly appreciated!

I think you can do this:
{
"apiVersion": "2018-05-01",
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('serviceBusNamespaceName'), copyIndex())]",
"resourceGroup": "[parameters('sharedResourcesResourceGroupName')]",
"copy": {
"name": "topics",
"count": "[length(parameters('topics'))]"
},
"dependsOn": [
"[parameters('serviceBusNamespaceName')]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "2018-05-01",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.ServiceBus/namespaces/topics",
"name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('topics')[copyIndex()])]",
"apiVersion": "2017-04-01",
"location": "[resourceGroup().location]",
"properties": {}
}
]
}
}
}

Related

How can I set dependencies on child resources in nested ARM template?

I am trying to use nested templates to deploy a resource group and multiple resources within it on subscription level.
Microsoft documentation has an example of deploying resource group and storage account that I'm trying to follow. I am trying to create another inner level of dependency between a Storage Account resource and a Container resource. That is, the container should only be deployed after the deployment of the storage account is finished. Here is simplified version of my template:
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"rgName": {
"type": "string"
},
"rgLocation": {
"type": "string"
},
"storagePrefix": {
"type": "string",
"maxLength": 11
},
"containerName": {
"type": "string"
}
},
"variables": {
"storageName": "[concat(parameters('storagePrefix'), uniqueString(subscription().id, parameters('rgName')))]"
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2021-04-01",
"name": "[parameters('rgName')]",
"location": "[parameters('rgLocation')]",
"properties": {}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2021-04-01",
"name": "storageDeployment",
"resourceGroup": "[parameters('rgName')]",
"dependsOn": [
"[resourceId('Microsoft.Resources/resourceGroups/', parameters('rgName'))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"name": "[variables('storageName')]",
"location": "[parameters('rgLocation')]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2"
},
{
"type": "Microsoft.Storage/storageAccounts/blobServices/containers",
"apiVersion": "2021-06-01",
"name": "[format('{0}/default/{1}', variables('storageName'), parameters('containerName'))]",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageName'))]"
]
}
],
"outputs": {}
}
}
}
],
"outputs": {}
}
When I try to deploy this template using PowerShell script New-AzSubscriptionDeployment, I get the following error:
| InvalidTemplate - Long running operation failed with status 'Failed'. Additional Info:'Deployment template validation failed: 'The resource 'Microsoft.Storage/storageAccounts/myStorageAccount' is not defined in the template. Please see https://aka.ms/arm-template for usage details.'.'
I kind of know it has to do with the dependsOn part of the container resource. But how can I resolve this problem?
EDIT: The selected answer solves the problem with dependencies, however the issue still persists in cases where a value needs to be called using concat or listKeys expressions. Here's an example where setting the value for AzureWebJobsStorage throws an error in a nested template:
{
"type": "Microsoft.Web/sites",
[ ... ]
"dependsOn": [
"[variables('hostingPlanName')]",
"[variables('functionAppStorageAccountName')]"
],
"properties": {
"serverFarmId": "[variables('hostingPlanName')]",
"siteConfig": {
"appSettings": [
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('functionAppstorageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('functionAppstorageAccountName')), '2021-04-01').keys[0].value)]"
}
[ ... ]
]
}
The value for AzureWebJobsStorage causes the deployment to fail with the following error:
`Status Message: The Resource 'Microsoft.Storage/storageAccounts/stfuncaedotestfeb16g' under resource group '<null>' was not found. For more details
| please go to https://aka.ms/ARMResourceNotFoundFix (Code:ResourceNotFound) CorrelationId: 55942377-6d0f-40ec-9733-33b9c3ea13de
I tried being more verbose by using resource group name (and then subscription ID), but that didn't solve the problem.
You should be able to use:
"dependsOn": [
"[variables('storageName')]"
]
Note that will only work if there is no other resource in the template with the same name - otherwise you have to manually construct the full resourceId, like:
[format('{0}/resourceGroups/{1}/providers/Microsoft.Storage/storageAccounts/{2}', subscription().id, parameters('rgName'), variables('storageName'))]
The latter form will always work, just a bit more verbose.
A bit more detail is that the resourceId function doesn't work as you would expect at subscription scope.

Creating storage queue subscription to custom Event Grid Topic via ARM

I'm trying to set up an Event Grid subscription to my Storage Queue from a custom topic.
This is easy to do when navigating in the portal, but I'm failing to create the appropriate ARM template for this. After having searched and tried out a lot, I've come up with the following piece of template.
{
"name": "MyCustomTopicName/Microsoft.EventGrid/MySubscriptionName",
"type": "Microsoft.EventGrid/topics/providers/eventSubscriptions",
"location": "[resourceGroup().location]",
"apiVersion": "2019-06-01",
"properties": {
"destination": {
"endpointType": "StorageQueue",
"properties": {
"resourceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('theNameOfMyStorageAccount'))]",
"queueName": "[variables('theNameOfMyQueue')]"
}
},
"filter": {
"advancedFilters": []
},
"labels": [],
"eventDeliverySchema": "EventGridSchema"
}
}
This looks rather OK to me but fails because the Event Grid topic isn't in the resource group to which I'm deploying the template.
Deployment failed. Correlation ID: [guid]. {
"error": {
"code": "ResourceNotFound",
"message": "The Resource 'Microsoft.EventGrid/topics/MyCustomTopicName' under resource group 'TheResourceGroupTheStorageAccountIsIn' was not found."
}
}
I'm deploying the complete ARM template to TheResourceGroupTheStorageAccountIsIn.
The MyCustomTopicName topic is in a resource group where we place the custom topics, so all services can use it.
I've tried using the full identifier (resource id) of the custom topic, but this isn't valid. Ideas?
PS: I'm using a similar template for creating a subscription to an Azure Function, which does work properly. The main difference over there is the destination block, which makes sense.
If I'm reading this right you just need to use a nested deployment and target the resource group where the topic is in:
{
"apiVersion": "2017-05-10",
"name": "nestedTemplate",
"type": "Microsoft.Resources/deployments",
"resourceGroup": "your_topic_resource_roup",
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"name": "MyCustomTopicName/Microsoft.EventGrid/MySubscriptionName",
"type": "Microsoft.EventGrid/topics/providers/eventSubscriptions",
"location": "[resourceGroup().location]",
"apiVersion": "2019-06-01",
"properties": {
"destination": {
"endpointType": "StorageQueue",
"properties": {
"resourceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('theNameOfMyStorageAccount'))]",
"queueName": "[variables('theNameOfMyQueue')]"
}
},
"filter": {
"advancedFilters": []
},
"labels": [],
"eventDeliverySchema": "EventGridSchema"
}
}
]
}
}
},

How do I use dependsOn on a copyIndex (array) resource?

I'm using an arm template to deploy topics and subscriptions. My resources to be deployed are of type Microsoft.Resources/deployments because I'm targeting a resource group external to where the deployment lives.
If the topics already exists, the template works.
Because arm template resources are deploy in parallel, I need to have the topics deploy before the subscriptions - this ordering is made possible by dependsOn.
The problem I'm having is that because the "name" value of the topics have a copyIndex(), I'm not sure how I can target the topic resource.
Among the many things I have tried, here are some:
[concat(parameters('serviceBusNamespaceName'), '/', parameters('subscriptions')[copyIndex()].topic)]
[resourceId('Microsoft.Resources/deployments',
parameters('topics'))]
["topicLoop"]
Here are the topics and subscriptions resource objects in my template:
{
"apiVersion": "2018-02-01",
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('serviceBusNamespaceName'), copyIndex())]",
"resourceGroup": "[parameters('sharedResourcesResourceGroupName')]",
"copy": {
"name": "topicLoop",
"count": "[length(parameters('topics'))]"
},
"properties": {
"mode": "Incremental",
"template": {
"$schema": "2018-05-01",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.ServiceBus/namespaces/topics",
"name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('topics')[copyIndex()])]",
"apiVersion": "2017-04-01",
"location": "[resourceGroup().location]",
"properties": {}
}
]
}
}
},
{
"apiVersion": "2018-02-01",
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('subscriptions')[copyIndex()].topic, copyIndex())]",
"resourceGroup": "[parameters('sharedResourcesResourceGroupName')]",
"copy": {
"name": "subscriptionLoop",
"count": "[length(parameters('subscriptions'))]"
},
"properties": {
"mode": "Incremental",
"template": {
"$schema": "2018-05-01",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.ServiceBus/namespaces/topics/subscriptions",
"name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('subscriptions')[copyIndex()].topic, '/', parameters('subscriptions')[copyIndex()].subscription)]",
"apiVersion": "2017-04-01",
"location": "[resourceGroup().location]",
"properties": {}
}
]
}
},
"dependsOn": [
// what goes here?! I need to have this depend on the topics
]
}
The dependsOn value is simply:
"dependsOn" : ["topicLoop"]
But it needed to be on the most external resource and not nested template.
yes, you can use dependsOn for each individual resource if you can generate its name. or you can hardcode those. either way, you can dependsOn the whole loop by using its name or individual resources inside the loop by referencing them by their name

Reference an existing Azure Automation Account in a different Resource Group

I need to use an existing Azure Automation Account in ARM Templates to create a new compilation job. I know how to do this when the Automation Account is in the same Resource Group where I'm deploying, but I can't figure it out when it's an existing Automation Account in a different Resource Group.
for example:
Parent Template (Resource)
{
"name": "dscCompile",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2016-09-01",
"dependsOn": [
"[resourceId('Microsoft.Resources/deployments', 'newGuid')]"
],
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('templates').dsc]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"compile-settings": {
"value": {
"configurationData": "[concat('{\"AllNodes\": [{\"NodeName\":\"*\",\"PSDscAllowPlainTextPassword\":true,\"RetryIntervalSec\":30,\"RetryCount\":20},{\"Nodename\":\"localhost\",\"domainName\":\"', parameters('extn-settings').domain, '\",\"adminCreds\":\"', parameters('adminPassword'), '\",\"Role\":\"DC\"}]}')]",
"configurationName": "createPDC",
"location": "Australia Southeast",
"name": "[reference('newGuid').outputs.guid.value)]"
}
},
"tag-values": {
"value": "[parameters('tag-values')]"
}
}
}
Child Template
$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"compile-settings": {
"type": "object",
"metadata": {
"description": "These are settings for a DSC Compile"
}
},
"tag-values": {
"type": "object",
"metadata": {
"description": "These are the Tag values"
}
}
},
"resources": [
{
"name": "[parameters('compile-settings').jobGuid]",
"type": "Microsoft.Automation/automationAccounts/compilationjobs",
"apiVersion": "2015-10-31",
"location": "[parameters('compile-settings').location]",
"tags": "[parameters('tag-values')]",
"dependsOn": [],
"properties": {
"configuration": {
"name": "[parameters('compile-settings').configurationName]"
},
"parameters": {
"ConfigurationData": "[parameters('compile-settings').ConfigurationData]"
}
},
"resources": []
}
],
"outputs": {}
}
Thanks in advance!
Okay, so this wasn't even possible until recently, you can do it with cross resource group deployment.
Basically, you create a template inside a template (aka nested\child template) and pick a different resource group (using the resourceGroup property for that template. no other way.
{
"apiVersion": "2017-05-10",
"name": "nestedTemplate",
"type": "Microsoft.Resources/deployments",
"resourceGroup": "crossResourceGroupDeployment",
"properties": { }
}

How to create a new resource in Azure ARM templates?

Ok, I've done everything what described here with ARM template - https://azure.microsoft.com/en-us/documentation/articles/web-sites-integrate-with-vnet. Except one thing - Enabling VNET Integration with a pre-existing VNET.
Can this be done in ARM templates?
Thanks!
Here is a sample template that might help you. It's modified from this quickstart sample in GitHub
{
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"hostingPlanName": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Name of the hosting plan to use in Azure."
}
},
"webSiteName": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Name of the Azure Web app to create."
}
},
"vnetName": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Name of an existing Azure VNet which has a Gateway Subnet already, and is in the resource group you are going to deploy."
}
},
"skuName": {
"type": "string",
"defaultValue": "S1",
"allowedValues": [
"S1",
"S2",
"S3",
"P1",
"P2",
"P3",
"P4"
],
"metadata": {
"description": "Describes plan's pricing tier and instance size. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/"
}
},
"skuCapacity": {
"type": "int",
"defaultValue": 1,
"minValue": 1,
"metadata": {
"description": "Describes plan's instance count"
}
}
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "[parameters('hostingPlanName')]",
"type": "Microsoft.Web/serverfarms",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "HostingPlan"
},
"sku": {
"name": "[parameters('skuName')]",
"capacity": "[parameters('skuCapacity')]"
},
"properties": {
"name": "[parameters('hostingPlanName')]"
}
},
{
"apiVersion": "2015-08-01",
"name": "[parameters('webSiteName')]",
"type": "Microsoft.Web/sites",
"location": "[resourceGroup().location]",
"tags": {
"[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource",
"displayName": "Website"
},
"dependsOn": [
"[concat('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
],
"properties": {
"name": "[parameters('webSiteName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "web",
"type": "config",
"dependsOn": [
"[concat('Microsoft.Web/sites/', parameters('webSiteName'))]"
],
"properties": {
"pythonVersion": "3.4"
}
},
{
"apiVersion": "2015-08-01",
"name": "[parameters('vnetName')]",
"type": "virtualNetworkConnections",
"location": "[resourceGroup().location]",
"dependsOn": [
"[concat('Microsoft.Web/sites/', parameters('webSiteName'))]"
],
"properties": {
"vnetResourceId": "[concat(resourceGroup().id, '/providers/Microsoft.Network/virtualNetworks/', parameters('vnetName'))]"
}
}
]
}
]
}
Here are 3 things you should be careful with.
The template starts with a python web app template, and adds a "Microsoft.Web/sites/virtualNetworkConnections" resource. So, if you are using other programing language, you can start with some other template.
The pre-existing VNet should be in the same resource group you are deploying. If the VNet you are using are not in the same resource group, you should modify the "vnetResourceId" in the "properties" of the "Microsoft.Web/sites/virtualNetworkConnections".
The VNet you are using should have a Gateway with a Point-to-Site address already. Otherwise, you will not be able integrate you web app to the VNet. For more details, see Configure a Point-to-Site connection to a virtual network using PowerShell
Update: About how I get this info, well, there is not much about this on the net. This template is constructed based on the PowerShell solution and my knowledge about ARM template. The PowerShell solution is available in this article. Another possible way to get an ARM template is to create those resources in one resource group, and export the template of the resource group in the portal. But, for this case, that is not going to work, because resource type "Microsoft.Web/sites/virtualNetworkConnections" is not supported yet. However, you can still get a look at the REST API by the PowerShell Command Get-AzureRmResource with option -debug.
Get-AzureRmResource -ResourceGroupName <resource group> -ResourceType Microsoft.Web/sites/virtualNetworkConnections -Name <web app>/<VNet> -debug -ApiVersion 2015-08-01
You will get the following REST API.
Uri:
https://management.azure.com/subscriptions/<subscription id>/resourceGroups/<resource group>/providers/Microsoft.Web/sites/<web app>/virtualNetworkConnections/<VNet>?api-version=2015-08-01
Body:
{
"id": "/subscriptions/<subscription id>/resourceGroups/<resource group>/providers/Microsoft.Web/sites/<web app>/virtualNetworkConnections/<VNet>",
"name": "<VNet>",
"type": "Microsoft.Web/sites/virtualNetworkConnections",
"location": "<Location>",
"tags": null,
"properties": {
"vnetResourceId": "/subscriptions/<subscription id>/resourceGroups/<resource group>/providers/Microsoft.Network/virtualNetworks/<VNet>"
"certThumbprint": "<Thumbprint>",
"certBlob": "<cert>",
"routes": null,
"resyncRequired": false,
"dnsServers": null
}
}
Skipping some automatically generated values, you will get the template which is quite similar to the one I write:
{
"name": "<VNet>",
"type": "Microsoft.Web/sites/virtualNetworkConnections",
"location": "<Location>",
"properties": {
"vnetResourceId": "/subscriptions/<subscription id>/resourceGroups/<resource group>/providers/Microsoft.Network/virtualNetworks/<VNet>"
}
}

Resources