Arm template: concatenate each element of an array with constant value - azure

When creating an arm template is it possible to concatenate each element of an array with a constant string? Below is my created parameter and the resource I am trying to create.
"parameters": {
"servicesNames": {
"type": "array",
"defaultValue": [
"test-api-content",
"test-svc-content"
]
}
}
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[concat(parameters('servicesNames'),'pip')]",
"location": "[resourceGroup().location]",
"copy": {
"name": "PIPaddresscopy",
"count": "[length(parameters('servicesNames'))]"
},
"tags": {
"displayName": "PublicIPAddress"
}
}
I would like the output of the resource name to be created with
"test-api-contentpip"
How ever I am getting the following error
The provided parameters for language function 'concat' are invalid.
Either all or none of the parameters must be an array
Please suggest how I can concatenate each value of the element

Just to add to existing answer (as it is a bit unclear in my opinion).
What you are trying to do with your code - concatenate array with a string, and what you need to do is concatenate each element of the array with string.
There's a copyIndex() function that represent current iteration of the loop. and you can use array[number] to access specific member of the array. so
parameters('servicesNames')[copyIndex()]
means parameters('servicesNames')[0] and parameters('servicesNames')[1] in your case. That would effectively mean you've iterated over this array.

You can concatenate each value of the element by modifying your name property for your publicIpAddress resource as below.
"name": "[concat(parameters('servicesNames')[copyIndex()], 'pip')]",
copyIndex:
This function is always used with a copy object.
If no value is provided for offset, the current iteration value is returned. The iteration value starts at zero.

Related

Trying to deploy naming convention in bicep and fails at the portal

I am going parameter file for each environment dev, prod, stg and calling respective parameter with the ps1 by changing the -templateparameter and the environment value.
I am passing index for env, approle and location from the parameter. Intellisense passes.
Eg for location in the main module passing locationShortName: locationList[locationIndex].locationShortname
Dev Parameter file
"locationIndex": {
"value": 1
}
locationList": {
"value": [
{
"Location": "westus2",
"LocationShortName": "azw2"
},
{
"Location": "eastus",
"LocationShortName": "aze"
},
{
"Location": "westus",
"LocationShortName": "azw"
},
{
"Location": "centralus",
"LocationShortName": "azc"
},
{
"Location": "westus3",
"LocationShortName": "azw3"
}
]
}
I get -
The language expression property array index '1' is out of bounds. Even though it should be picking up 'azw2'for 'westus2' shortname on index 1
Trying to call array and object value from parameter file to main module and pass it to submodule.
At the portal it shows - property array index '1' is out of bounds. Even though it should be picking up 'azw2'for 'westus2' shortname on index 1your text
The language expression property array index '1' is out of bounds.
This issue usually occurs when you try to access an index position or array element that is greater than the array element's length. Moreover, if you want to access locationshort azw2, you would pass array index as 0 because array length is taken from the 0-index position.
I tried below output block to retrieve the array elements with the help of index values by adding a for loop.
output locationList array = [for (location, i) in locationList :{
locationList : <ResourceName>.properties.locationList[i]
}]
After a workaround on this, I implemented a sample script for naming conventions in bicep by referring to SO by #Ansuman Bal and made a few changes and was deployed successfully.
resource exampleResource 'Microsoft.Network/virtualNetworks#2019-11-01' = {
name: ResourceName
location: location
properties: {
}
locationList: locationList
}
output locationList array = [for (location, i) in locationList :{
locationList : <ResourceName>.properties.locationList[i]
}]
Output:

Reference Azure ARM template output variable from same deployment in another output variable

In an ARM Template, I have two output variables which are built by concating some strings. So, for the sake of it, let's just say it looks like this
"outputs": {
"firstKey": {
"type": "string",
"value": "[concat('first', 'Key')]"
},
"secondKey": {
"type": "string",
"value": "[concat('second', 'Key')]"
}
}
I now want to introduce a variable that concats these strings, like this
"bothKeys": {
"type": "string",
"value": "[concat('FirstKey=', outputs(firstKey), ';', 'SecondKey=', outputs(secondKey)]"
}
so I basically get these outputs
firstKey = firstKey
secondKey = secondKey
bothKeys = FirstKey=firstKey;SecondKey=secondKey
How would I do this?
I found various solutions to reference output variables from different deployments like [reference('deploymentName').outputs.propertyName.value] or [reference(resourceId('randomResource', variables('ResourceName'))).downloadLocation], but these are for referncing the outputs of OTHER deployments, where I want it from the same deployment.
Of course I could just copy paste the concat string but when I would edit one, I'd have to edit others as well. Is there a better way?
I couldn't find how to reference other output variables in the documentation listed here
https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/outputs?tabs=azure-powershell
https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/syntax#outputs

How to convert an array into properties of one object in an ARM template?

I am looking for a way to convert an array (e.g. of strings) into one object, where the properties are generated from the array values.
Use case: I want to generate a tags object with links to resources, based on a list of resource names. I need to do this, to link App Service resources to an Application Insights resource.
The list of resources could be supplied using a parameter:
"parameters": {
"appServices": {
"type": "array",
"metadata": {
"description": "Names of app services to link this application insights resource to via hidden tags"
}
}
}
Sample input:
['appName1', 'appName2', 'appName3']
Sample output:
"tags":
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName1'))]": "Resource",
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName2'))]": "Resource",
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName3'))]": "Resource"
}
I know you can use copy to loop over arrays but that will create an array of objects and not a single object (which is required for tags), for example:
[
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName1'))]": "Resource"
},
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName2'))]": "Resource"
},
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName3'))]": "Resource"
}
]
It would be possible to use union to merge those objects again, but that function requires you to hardcode the objects you want to merge, so it does not work when you have an input with variable length.
What I am looking for is a way to do this in a dynamic way.
There is no direct option to convert array to object.
But here's a hack to achieve what you need. This will work for array of any length.
Steps:
append hidden-link text to service names
convert array to string
replace necessary symbols and make it a valid json string.
use json() to convert string to object
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appServices": {
"type": "array",
"metadata": {
"description": "Names of app services to link this application insights resource to via hidden tags"
},
"defaultValue": [ "appName1", "appName2", "appName3" ]
}
},
"functions": [],
"variables": {
"copy": [
{
"name": "as",
"count": "[length(parameters('appServices'))]",
"input": "[concat('hidden-link:', resourceId('Microsoft.Web/sites/', parameters('appServices')[copyIndex('as')]))]"
}
],
"0": "[string(variables('as'))]",
"1": "[replace(variables('0'), '[', '{')]",
"2": "[replace(variables('1'), '\",', '\":\"Resource\",')]",
"3": "[replace(variables('2'), '\"]', '\":\"Resource\"}')]"
},
"resources": [],
"outputs": {
"op1": {
"type": "object",
"value": "[json(variables('3'))]"
}
}
}
Now that lambdas have been added to bicep you can convert arrays to objects using reduce. Note that the array you reduce must consist of object items. If it doesn't you can convert it to an object using map.
// With array of objects
var names = [
{
name: 'foo'
id: 'foo-id'
}
{
name: 'bar'
id: 'bar-id'
}
]
var nameIds = reduce(names, {}, (cur, next) => union(cur, {
'${next.name}': next.id
}))
output test object = nameIds
// With array of strings
var names = [
'foo'
'bar'
]
var nameMaps = map(names, (name) => {name: name})
var nameIds = reduce(nameMaps, {}, (cur, next) => union(cur, {
'${next.name}': next.name
}))
output test object = nameIds
I'm not sure if this is the best approach to this problem.
Tags are supposed to be metadata about a specific object/service. Wouldn't it make more sense to apply a tag (say your system name, environment, etc..) and then run a query against azure on that tag?
This should achieve the same result pulling back all related resources.
I don't know if it is still relevant, but since 2021 it is possible to do with items() function

ARM template, Incorrect Segment Lengths

I'm trying to build an ARM template and keep getting the error:
'The template resource 'udr-sub-w05-w05-w05-agw-10.10.10.32/27' for type
'Microsoft.Network/routeTables' at line '141' and column '5' has incorrect segment lengths. A nested resource type must have identical number of segments as its resource name. A root resource type must
have segment length one greater than its resource name.
The nested template for create route tables code is below:
{
"name": "[variables('routeTable1')]",
"type": "Microsoft.Network/routeTables",
"apiVersion": "[variables('routeTableApiVersion')]",
"location": "[resourceGroup().location]",
"properties": {
"routes": [
],
"disableBgpRoutePropagation": false
}
},
{
"name": "[variables('routeTable2')]",
"type": "Microsoft.Network/routeTables",
"apiVersion": "[variables('routeTableApiVersion')]",
"location": "[resourceGroup().location]",
"properties": {
"routes": [
],
"disableBgpRoutePropagation": false
}
},
Any idea where this is going wrong? I've spent some time googling and my understanding is the "TYPE" should have one less segment than the "NAME", which I believe it has
"name": "[variables('routeTable1')]",
"type": "Microsoft.Network/routeTables",
Route table one variables
"routeTable1": "[tolower(concat('udr-', variables('subnetName1')))]",
"routeTable2": "[tolower(concat('udr-', variables('subnetName2')))]",
Thanks
Your route table name contains /, hence it thinks you are trying to create a sub resource and asks you to provide its type (you only provide parent resource type). remove the /27 thing or replace it with -27 or something like that.

Create ARM parameter names dynamically

I have a scenario where i need to generate the parameters names dynamically. Like certificate1, certificate2, certificate3 .. so on. Currently all these parameters should be defined in Main template. Can we use copy to iterate and define parameter Names dynamically in Main/Parent template? Or is there a way in ARM templates by which this can be accomplished?
you can use copy construct in variables section or in resource definition\resource properties. and then you can use concat() together with copyIndex() function to create names.
example:
[concat('something-', copyIndex())]
this will give you names like something-0, something-1, something-2 etc. (copyIndex starts at 0). you can also choose to offset copyIndex by giving it an offset number:
[concat('something-', copyIndex(10))]
this will give you name like something-10, something-11, something-12 etc.
copy in variables\properties:
"copy": [
{
"name": "nameOfThePropertyOrVariableYouWantToIterateOver",
"count": 3,
"input": {
"name": "[concat('something-', copyIndex('nameOfThePropertyOrVariableYouWantToIterateOver', 1))]"
}
}
]
here you need to specify which loop are you referring to with copyIndex function and you can use offset as well
You can use the copy function in Azure Template to generate the name of the resource, just like certificate1, certificate2, certificate3 .. so on.
Example below:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"apiVersion": "2016-01-01",
"type": "Microsoft.Storage/storageAccounts",
"name": "[concat('storage',copyIndex())]",
"location": "[resourceGroup().location]",
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage",
"properties": {},
"copy": {
"name": "storagecopy",
"count": 3
}
}
],
"outputs": {}
}
And the storage name will like these:
storage0
storage1
storage2
For more details, see Deploy multiple instances of a resource or property in Azure Resource Manager Templates.

Resources