Can one get output from multiple (copy/copyIndex) ARM child Templates? - arm-template

I have a parent template that takes an array (websites) and invokes a childTemplate (website) multiple times using the copyIndex() method to loop through values in the array passed as a parameter.
Each of the child templates returns -- in its outputs -- the MSI principalId, as well as outgoingIPAddresses.
Is there a way one can assemble the returned individual principalId values into an array that can be used by subsequent child template invoked by the parent template (in order to loop through the resulting array of principalIds and give them all the same rights to a KeyVault)?
Thank you.

Yes, this can be done, although, not as pretty\easy as you would like it to.
easiest way to do this, is assemble an array of values you need using output from the templates. you need each your template to take the output from the previous one and concat it with its own output and spit the result as an output. sample code:
{
"name": "reference0", << this one is needed so that the first real one has something to reference, as it cant reference itself
"type": "Microsoft.Resources/deployments",
"apiVersion": "2015-01-01",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "yourtemplate",
"contentVersion": "1.0.0.0"
}
},
"parameters": {
"state": {
"value": []
}
}
},
{
"name": "[concat('reference', copyIndex(1))]",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2015-01-01",
"copy": {
"name": "loop",
"count": "[variables('types')[resourceGroup().name]]"
},
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "yourtemplate",
"contentVersion": "1.0.0.0"
},
"parameters": {
"state": {
"value": "[reference(concat('loop', copyIndex())).outputs.state.value]"
}
}
}
},
and your state output should just be something like this:
"outputs": {
"state": {
"type": "array",
"value": "[concat(parameters('state'), array(your_actual_output))]"
}

Related

Azure mainTemplate.json - access output from templateLink

I have a JSON deployment template with something like:
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"name": "parameters('storageAccounts')[copyIndex()].name",
"resourceGroup": "[resourceGroup().name]",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "'https://foo.blob.sa/StorageAccount/azuredeploy.json"
},
"parameters": {
...
}
},
azuredeploy.json creates the storage account then has something like:
"outputs": {
"storageAccountWebEndpoint": {
"type": "object",
"value": {
"tags": { ... },
"type": "string",
"value": "[reference(parameters('storageAccountName')).primaryEndpoints.web]"
}
},
Is it possible to leverage the output from the linked template to set a property for another resource in my deployment template?
If so, what would be the syntax?
(Assume I have dependsOn set correctly.)
You can use the function reference() to get the output in the link template:
"[reference('deploymentName').outputs.propertyName.value]"
But note that:
When getting an output property from a linked template, the property
name must not include a dash.
Get more details here.

Error when passing variable with special parameters to the ARM template

I have a master template and a couple of linked templates. I want to pass parameter with special characters and have an error. Anyone know how to fix it?
The problem is caused by 'servicebus_1_connectionString' parameter in last linked template definition. I provided the error and modified few letters in the secret so you have an overview but still not revealing my secrets.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"containerUri": {
"type": "string"
},
"containerSasToken": {
"type": "string"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2017-05-10",
"name": "AzureServiceBusLinkedTemplate",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[concat(parameters('containerUri'), 'Infrastructure/AzureServiceBus.json', parameters('containerSasToken'))]"
}
}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2017-05-10",
"name": "AppFunctionsLinkedTemplate",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[concat(parameters('containerUri'), 'Infrastructure/AppFunctions.json', parameters('containerSasToken'))]"
}
}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2017-05-10",
"name": "LogicAppLinkedTemplate",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[concat(parameters('containerUri'), 'LogicApp.json', parameters('containerSasToken'))]"
},
"parameters": {
"servicebus_1_connectionString": "[reference('AzureServiceBusLinkedTemplate').outputs.SBNamespaceDefaultConnectionString.value]"
}
},
"dependsOn": [
"AzureServiceBusLinkedTemplate"
]
}
],
"outputs": {
}
}
2020-02-26T09:47:34.3751880Z ##[error]At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.
2020-02-26T09:47:34.3754763Z ##[error]Details:
2020-02-26T09:47:34.3758785Z ##[error]BadRequest: {
"error": {
"code": "InvalidRequestContent",
"message": "The request content was invalid and could not be deserialized: 'Error converting value \"Endpoint=sb://myservicebusname.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=I/COoOJCWH/PFMab0dzpseIbfA3+0sQMUj33d71/Rg4=\" to type 'Microsoft.WindowsAzure.ResourceStack.Frontdoor.Data.Definitions.DeploymentParameterDefinition'. Path 'properties.parameters.servicebus_1_connectionString', line 1, position 462.'."
}
}
2020-02-26T09:47:34.3786900Z ##[error]Task failed while creating or updating the template deployment.
Edit:
I solved it by changing the way I pass parameter to the following:
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2017-05-10",
"name": "LogicAppLinkedTemplate",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[concat(parameters('containerUri'), 'LogicApp.json', parameters('containerSasToken'))]"
},
"parameters": {
"servicebus_1_connectionString": {
"value": "[reference('AzureServiceBusLinkedTemplate').outputs.SBNamespaceDefaultConnectionString.value]"
},
"logicAppName": {
"value": "DeployedFromVS"
}
}
},
"dependsOn": [
"AzureServiceBusLinkedTemplate"
]
}
several options:
which makes more sense, don't pass it, just use the value in the nested template
bonus: instead of retrieving the value of the deployment output - retrieve the value of the connection string in the nested template. makes a lot more sense and is more secure
base64 encode it and decode it in the nested template. there is a base64encode\decode function for that
I solved it by changing the way I pass parameter to the following:
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2017-05-10",
"name": "LogicAppLinkedTemplate",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[concat(parameters('containerUri'), 'LogicApp.json', parameters('containerSasToken'))]"
},
"parameters": {
"servicebus_1_connectionString": {
"value": "[reference('AzureServiceBusLinkedTemplate').outputs.SBNamespaceDefaultConnectionString.value]"
},
"logicAppName": {
"value": "DeployedFromVS"
}
}
},
"dependsOn": [
"AzureServiceBusLinkedTemplate"
]
}

creating a new guid at each iteration in an ARM template

I try to script an ARM template like this:
{
"type": "Microsoft.EventHub/namespaces/ipfilterrules",
"apiVersion": "2018-01-01-preview",
"name": "[concat(parameters('name'), '/fr', copyIndex())]",
"location": "[resourceGroup().location]",
"properties": {
"ipMask": "[parameters('ipMasks')[copyIndex()]]",
"action": "Accept",
"filterName": "[concat('fr', copyIndex())]"
},
"copy": {
"name": "eventHubfirewallrulescopy",
"count": "[length(parameters('ipMasks'))]"
}
What I really want to to is to feed "name" and "filtername" by the same GUID value. in order to have something like this:
{
"type": "Microsoft.EventHub/namespaces/ipfilterrules",
"apiVersion": "2018-01-01-preview",
"name": "[concat(parameters('name'), 'd8fb1ab2-a6cb-439f-9354-918bd96d6ace')]",
"location": "[resourceGroup().location]",
"properties": {
"ipMask": "[parameters('ipMasks')[copyIndex()]]",
"action": "Accept",
"filterName": "d8fb1ab2-a6cb-439f-9354-918bd96d6ace"
},
"copy": {
"name": "eventHubfirewallrulescopy",
"count": "[length(parameters('ipMasks'))]"
}
As you can see this is a multi deployement resource. So I need a new guid generated at each iteration.
I first thought to create a variable using guid() function, but it is not permitted.
Is there any other idea?
thank you
How about defining a proxy parameter in the template for which you do not pass the value and is initialed to default new guid and use it in the variables later to generate the unique string? Something like this
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"guidValue": {
"type": "string",
"defaultValue": "[newGuid()]"
}
},
"variables": {
"unique_string":"[uniqueString(parameters('guidValue'))]"
},
"resources":
[
.. your resources with names derived from variables('unique_string')
],
"outputs": {
"unique_string": {
"type": "string",
"value": "[variables('unique_string')]"
}
}
}
Hope this helps?
If the length of parameters('ipMasks') is known you can use either a pregenerated array of guids with the same length and then use copyIndex to pick the guid in the same way that you pick the ip masks. If a random guid is needed (in my case a static guid was needed) the same scenario with a generated guid list should work:
[
"[newGuid()]",
"[newGuid()]",
"[newGuid()]",
"[newGuid()]",
"[newGuid()]"
]
Assuming a fixed array parameter of 5 items.

Configure FirewallRules for all PossibleOutboundIpAddresses in ARM Template

I would like to create firewall rules so that only my Azure Web App can connect to my database. If possible, I'd like to do this in my ARM template. Here's what I have tried so far:
{
"variables": {
"defaultResourceName": "[resourceGroup().name]",
},
"resources": [
{
"type": "Microsoft.Web/sites/firewallRules",
"name": "[concat('AllowAzureIpAddress', copyIndex()",
"apiVersion": "2015-05-01-preview",
"properties": {
"startIpAddress": "[reference('Microsoft.Web/sites', variables('defaultResourceName')).possibleOutboundIpAddresses[copyIndex()]]",
"endIpAddress": "[reference('Microsoft.Web/sites', variables('defaultResourceName')).possibleOutboundIpAddresses[copyIndex()]]"
},
"dependsOn": [
"[resourceId('Microsoft.Sql/servers/', toLower(variables('defaultResourceName')))]"
],
"copy": {
"name": "firewallRuleCopy",
"count": "[length(reference('Microsoft.Web/sites', variables('defaultResourceName')).possibleOutboundIpAddresses)]"
}
},
]
}
The main problem is getting the PossibleOutboundIpAddresses. I'm not sure if they're available to me here, and I'm getting an error when I try to validate my ARM Template that says The template function 'reference' is not expected at this location. Please see https://aka.ms/arm-template-expressions for usage details..
Has anyone done this that has any advice for how to go about getting those OutboundIpAddresses (preferably in a list so that copy can use them)?
your problem comes not from using reference function in a wrong fashion, but from the fact that you cant use reference function in copy property (copy is evaluated at "compile time" whereas reference at runtime, so it cannot evaluate length of the copy). your possible work around is: nested deployment. here's what I've been using:
{
"name": "firewallRules",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2015-01-01",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "https://paste.ee/d/Hkebg/0",
"contentVersion": "1.0.0.0"
},
"parameters": {
"prefix": {
"value": "[variables('prefix')]"
},
"iterator": {
"value": "[split(reference(concat(parameters('prefix'), '-', parameters('webAppNames').name), '2016-03-01', 'Full').properties.possibleOutboundIpAddresses, ',')]"
}
}
}
},

Inline object as a parameter value to a linked template deployment

I'd like to be able to pass an inline object as the value of a parameter to a linked template. The use case would be that I have a template that deploys a service bus (or some other resource) and a template that deploys a web application. I want to build a template that marries the two components. I'd like the web app template to have an object parameter called userProvidedAppSettings that I can union with some defaults and then assign that resulting object as the properties value of a Microsoft.Web/site/config/appsettings resource.
It appears that you can not currently use the reference or listkeys functions in an inline object value for a parameter, see the userProvidedAppSettings in the example below.
Is this possible and I'm not using a proper convention? I haven't seen anything in the documentation about this.
{
"apiVersion": "[parameters('apiVersion')]",
"name": "[variables('serviceBusDeploymentName')]",
"type": "Microsoft.Resources/deployments",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[parameters('templateOneUri')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"environment": { "value": "[parameters('environment')]" },
"appName": { "value": "[parameters('appName')]" }
}
}
},
{
"apiVersion": "[parameters('apiVersion')]",
"name": "[variables('applicationDeploymentName')]",
"type": "Microsoft.Resources/deployments",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[parameters('templateTwoUri')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"environment": { "value": "[parameters('environment')]" },
"appName": { "value": "[parameters('appName')]" },
"userProvidedAppSettings" : { "value": { "serviceBusConnectionString": "[reference(variables('serviceBusDeploymentName')).outputs.connectionString.value]" } }
}
}
}
EDIT:
To clarify, this is about the behavior of linked template parameter values. I am specifically asking about this:
"parameters": {
// Allowed:
"param1": { "value": "[parameters('environment')]" },
"param2": { "value": "[reference('otherDeployment').outputs.something.value]" },
"param3": { "value": { "this": "is allowed",
"inline": "is allowed" } },
// NOT Allowed
"param4": { "value": { "this": "is NOT allowed".
"foo": "[reference('otherDeployment').outputs.something.value]" } }
}
reference outputs are allowed as values, inline objects are allowed as values, but inline objects whose values include a reference (or implicit reference from list functions) are NOT allowed. I'm wondering if this is either possible through a different convention or if this should be added to a list of desired features.
For your issue, not sure but you can have a try about links and nested templates. You can get the value of the link template in the main template.
You can define the variable in the link template output and use it in the main template. There is a simple example here. Hope this will help you!
you could use a union() function to build the desired object and pass that as a value to the parameter.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"firstObject": {
"type": "object",
"defaultValue": {"one": "a", "two": "b", "three": "c1"}
},
"secondObject": {
"type": "object",
"defaultValue": {"three": "c2", "four": "d", "five": "e"}
}
},
"resources": [],
"outputs": {
"objectOutput": {
"type": "object",
"value": "[union(parameters('firstObject'), parameters('secondObject'))]"
}
}
}

Resources