I am deploying VNET before deploying other resources. It does deploy the first VNET template, but gives an error deploying others, as it says subnet is is provisioning state i.e. the resource is updating.
I am using nested templates and tried using dependsOn property in the ARM, although is not working. Is is possible to use it at the resource level?
"resources": [
{
"apiVersion": "2017-05-10",
"name": "vNet_ResourceUnit",
"type": "Microsoft.Resources/deployments",
"resourceGroup": "[resourceGroup().name]",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('vnetTemplateUrl')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"hyphenBasedPrefix": {
"value": "[variables('hyphenBasedPrefix')]"
},
"baseTemplateUrl": {
"value": "[parameters('baseTemplateUrl')]"
},
"vnetObject": {
"value": "[variables('vnet')]"
}
}
}
},
{
"apiversion": "2017-05-10",
"name": "keyVault_resourceunit",
"type": "microsoft.resources/deployments",
"resourcegroup": "[resourcegroup().name]",
"dependsOn": [
------
],
"properties": {
"mode": "incremental",
"templatelink": {
"uri": "[variables('keyVaultTemplateUrl')]",
"contentversion": "1.0.0.0"
},
"parameters": {
"hyphenbasedprefix": {
"value": "[variables('hyphenbasedprefix')]"
},
"basetemplateurl": {
"value": "[parameters('basetemplateurl')]"
},
"keyvaultobject": {
"value": "[variables('keyvault')]"
},
"vnetObject": {
"value": "[variables('vnet')]"
}
}
}
}
]
How can i use the dependsOn property here at resource level? I did try at the last template using :
"[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]"
But its not working.
How can i use it in the 'keyVault_resourceunit' itself?
Instead of using a resource id in the dependency, try using the name of the resource object i.e. the value "vNet_ResourceUnit" from line 3 of the code in your question.
"dependsOn": [
"vNet_ResourceUnit"
]
The effect that has is to make the arm process wait until that resource deployment (named "vNet_ResourceUnit") has completely finished, before starting your keyVault_resourceunit deployment.
you need to wait for the deployment to finish, not the resources inside the deployment (because they are in a different deployment, template doesnt know anything about them).
"[resourceId('Microsoft.Resources/deployments', 'vNet_ResourceUnit')]"
Related
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.
We want to have two resource group, 1- shared-resource group for shared resources like app service plan, ...
2- another resource-group for other resources like web-app.
We are using nested link template which contains three level:
highest level is main deployment template file which contains everything.
next level is for calling all resource templates for each resource-group, e.g we have two template file in this level one for shared resource-group and two other resource-group, so for-example first file will call some link template to create app service plan, elastic pool and ....
second file will call link templates for deploying web-app, network and ....
last level is our generic resource templates, in this level for each resource we have a template file e.g for web-app we have a generic template which deploy web-app with some parameters.
Now the question is how we can use 'dependson' to create all shared-Resources first after that deploy all other resources?
*please note: shared resources are in different resource-group and other resources are in another resource-group so we have two resource-group which both and all included resources are deploying in main template file.
My main deployment template file is this:
// Include rg-CockpitShared and app specific resources - and share parameters
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-
01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string"
},
"cockpitEnvironment": {
"type": "string"
}
},
"variables": {
"currentTemplateUri": "[deployment().properties.templateLink.uri]",
"rgCockpitSharedUrl": "[concat(uri(variables('currentTemplateUri'), 'rg-CockpitShared/rg-CockpitShared.json'))]",
"rgCockpitSharedParametersUrl": "[concat(uri(variables('currentTemplateUri'), concat('rg-CockpitShared/rg-CockpitShared.parameters.', parameters('cockpitEnvironment'), '.json')))]",
"rgClientCmdbTemplateUrl": "[concat(uri(variables('currentTemplateUri'), 'rg-ClientCmdb/rg-ClientCmdb.json'))]",
"rgClientCmdbParametersUrl": "[concat(uri(variables('currentTemplateUri'), concat('rg-ClientCmdb/rg-ClientCmdb.parameters.', parameters('cockpitEnvironment'), '.json')))]"
},
"resources": [
{
"type": "Microsoft.Resources/deployments",
"location": "[parameters('location')]",
"apiVersion": "2019-10-01",
"name": "CockpitShared",
"tags": {
"devOwner": "aspBuild"
},
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('rgCockpitSharedUrl')]",
"contentVersion": "1.0.0.0"
},
"parametersLink": {
"uri": "[variables('rgCockpitSharedParametersUrl')]",
"contentVersion": "1.0.0.0"
}
}
},
{
"type": "Microsoft.Resources/deployments",
"location": "[parameters('location')]",
"apiVersion": "2019-10-01",
"name": "otherResources",
"tags": {
"devOwner": "aspBuild"
},
"dependsOn": [
"CockpitShared"
],
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('rgClientCmdbTemplateUrl')]",
"contentVersion": "1.0.0.0"
},
"parametersLink": {
"uri": "[variables('rgClientCmdbParametersUrl')]",
"contentVersion": "1.0.0.0"
}
}
}
],
"outputs": {
"rgCockpitSharedUrl": {
"type": "string",
"value": "[variables('rgCockpitSharedUrl')]"
},
"rgCockpitSharedParametersUrl": {
"type": "string",
"value": "[variables('rgCockpitSharedParametersUrl')]"
}
}
}
So What I have done here is using dependsOn = "CockpitShared" and expected that first it deploys shared resources and after that start to deploy 'otherResources', but I got error that app-service-plan is not found which means it's not wait to shared resources be deployed and I don't understand why? :(
Finally I found a solution:
I used 'reference' instead of 'dependson'.
So in my shared-Resources arm template I return app service plan name as output and used it from my main deployment file so it wait for finishing shared resources deployment first and after that start to deploy web app ...
for more detail see below code:
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string"
},
"cockpitEnvironment": {
"type": "string"
},
"storageAccountUri": {
"type": "string"
}
},
"variables": {
"rgCockpitSharedUrl": "[concat(uri(parameters('storageAccountUri'), 'rg-CockpitShared/rg-CockpitShared.json'))]",
"rgClientCmdbTemplateUrl": "[concat(uri(parameters('storageAccountUri'), 'ClientCmdb/rg-ClientCmdb/rg-ClientCmdb.json'))]"
},
"functions": [
{
"namespace": "ClientCmdb",
"members": {
"SelectValueByEnv": {
"parameters": [
{
"name": "Environment",
"type": "string"
},
{
"name": "prodValue",
"type": "string"
},
{
"name": "devValue",
"type": "string"
},
{
"name": "testValue",
"type": "string"
}
],
"output": {
"type": "string",
"value": "[if(equals(parameters('Environment'),'prod'),parameters('prodValue'),if(equals(parameters('Environment'),'dev'),parameters('devValue'),parameters('testValue')))]"
}
}
}
}
],
"resources": [
{
"type": "Microsoft.Resources/deployments",
"location": "[parameters('location')]",
"apiVersion": "2019-10-01",
"name": "CockpitShared",
"tags": {
"devOwner": "aspBuild"
},
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('rgCockpitSharedUrl')]"
},
"parameters": {
"rgName": {
"value": "[ClientCmdb.SelectValueByEnv(parameters('cockpitEnvironment'),'rg-CockpitShared','rg-CockpitShared','rg-CockpitShared')]"
},
"location": {
"value": "[parameters('location')]"
},
"cockpitEnvironment": {
"value": "[parameters('cockpitEnvironment')]"
}
}
}
},
{
"type": "Microsoft.Resources/deployments",
"location": "[parameters('location')]",
"apiVersion": "2019-10-01",
"name": "ClientCmdb",
"tags": {
"devOwner": "aspBuild"
},
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('rgClientCmdbTemplateUrl')]"
},
"parameters": {
"rgName": {
"value": "[ClientCmdb.SelectValueByEnv(parameters('cockpitEnvironment'),'rg-ClientCmdb','rg-ClientCmdb','rg-ClientCmdb')]"
},
"location": {
"value": "[ClientCmdb.SelectValueByEnv(parameters('cockpitEnvironment'),'northeurope','northeurope','northeurope')]"
},
"cockpitEnvironment": {
"value": "[parameters('cockpitEnvironment')]"
},
"planName": {
// Here__________________________________________________________________________________
"value": "[reference('CockpitShared').outputs.planName.value]"
}
}
}
}
],
}
so basically I pass my app service plan name from shared resource template to main template and main template gets it by 'reference' and pass it to other resources template via parameter.
Regarding to do that I had to use inline params as I was using params in seperated files...
Hope it's useful for someone else :)
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"
}
}
]
}
}
},
Below I have a (simplified) Azure ARM Template to deploy a website which has in its appSettings the storage account. I originally passed the key via a string output parameter which works fine.
Storage Template
"outputs": {
"storageKey": {
"type": "string",
"value": "[listKeys(resourceid(resourceGroup().name, 'Microsoft.Storage/storageAccounts', parameters('storageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value]"
}
}
Root template
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Resources/deployments",
"name": "[concat(resourceGroup().Name, '-', variables('tdfConfiguration')[copyIndex()]['roleName'], '-storage')]",
"copy": {
"name": "storageCopy",
"count": "[length(variables('tdfConfiguration'))]"
},
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('storageAccountTemplateUri')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"storageAccountName": { "value": "[variables('tdfConfiguration')[copyIndex()]['componentName']]" },
"storageAccountLocation": { "value": "[resourceGroup().location]" },
"storageAccountType": { "value": "[variables('storageAccountType')]" }
}
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Resources/deployments",
"name": "[concat(resourceGroup().Name, '-', variables('tdfConfiguration')[copyIndex()]['roleName'], '-website')]",
"copy": {
"name": "webSiteCopy",
"count": "[length(variables('tdfConfiguration'))]"
},
"dependsOn": [
"[concat('Microsoft.Resources/deployments/', resourceGroup().Name, '-', variables('tdfConfiguration')[copyIndex()]['roleName'], '-serviceplan')]",
"[concat('Microsoft.Resources/deployments/', resourceGroup().Name, '-', variables('tdfConfiguration')[copyIndex()]['roleName'], '-storage')]"
],
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('webSiteTemplateUri')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"azureWebJobsStorageAccountKey": { "value": "[reference(concat(resourceGroup().Name, '-', variables('tdfConfiguration')[copyIndex()]['roleName'], '-storage')).outputs.storageKey.value]" }
}
}
},
I'm worried that passing that around as a string may expose it in some deployment logs. However if I switch it to a securestring output parameter I can no longer access the value. So it seems like I need to listkeys in the root template, but if I change my website parameters to
"azureWebJobsStorageAccountKey": { "value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts',variables('tdfConfiguration')[copyIndex()]['componentName']), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value]" }
it fails because even though the storage account is listed as a dependsOn, it attempts to resolve this value immediately without waiting for the storage account to be deployed. Any ideas?
list* functions will wait for the resource to be available if it is being created in the same template. but you are using nested templates so it has no way of knowing the resource is provisioned or not, so it just assumes its provisioned (standard behavior when you use list* functions).
dont pass it in as a value (it makes no sense really), just use the same expression inside the deployment and it will work. because the deployment will start only after the previous deployment was finished and the storage account will be there already.
Also, I dont see why you are doing this with nested templates, I dont see any reason for doing this in your case, you are over complicating your deployment\code for no benefit (and even created a problem for yourself because of that). Just drop nested deployments and use resources.
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, ',')]"
}
}
}
},