Looping through complex JSON variables in ARM templates - azure

In my ARM template I have a variable called "subnets" which can be of 3 types.
If it is of typeA then I want 4 subnets of the given names and addresses; if it's typeB then 2 subnets, and so on.
"variables": {
"subnets" : {
"typeA" : {
"network" : "3.0/24",
"directory" : "5.0/24",
"documents" : "8.0/24",
"security" : "10.0/24",
},
"typeB" : {
"directory" : "10.0/24",
"database" : "11.0/24",
},
"dmz" : {
"directory" : "12.0/24",
"database" : "15.0/24", }
}
}
In the ARM template I have a parameter which tells me what type to use. So I have a segment like the below which uses a condition to match on the subnetType being typeA and creates a virtual network accordingly.
{
"type": "Microsoft.Network/virtualNetworks",
"condition" : "[contains(parameters('subnetType'), 'typeA')]",
"apiVersion": "2018-10-01",
...
"copy" : [ {
"name" : "subnets",
"count" : "[length(array(variables('subnets').typeA))]",
"input": {
"name": "...",
"properties": {
"addressPrefix": "..."
}
}
} ]
}
}
As you can see above, I have a copy block within this VirtualNetwork resource, and I want to create the various subnets for the typeA network. I figure I could convert subnets.typeA to an array and get the length of it to loop over (that's the idea, I don't know if it actually works) but I am not clear how to extract the subnet name and addressprefix from my variable above.

so there are 2 issues here:
no way to loop object keys in arm templates
use of different resources in the template to create subnets
there is no way to work around the first limitation that I know of, whereas the second limitation is mostly due to you trying to work around the first one. I'd go for a completely different approach:
"networks": [
{
"name": "typeA",
"subnets": [
{
"name": "network",
"addressSpace": "3.0/24"
},
{
"name": "directory",
"addressSpace": "5.0/24"
},
{
"name": "documents",
"addressSpace": "8.0/24"
},
{
"name": "security",
"addressSpace": "10.0/24"
}
]
},
{
// second virtual network
},
{
// x virtual network
}
]
the main downside here - you'd have to have a nested deployment, because you cannot actually iterate array inside array, so you'd have to feed each object inside array into a deployment that would create a virtual network that can contain various subnets.
You can consult this link for an example of this exact approach or the official Azure Building Blocks thingie way of doing this (which is quite similar in the approach, but the implementation is different).
You could, get away with different resources instead of iterations, but that means you are less flexible and each time you make a change to the input everything breaks or just doesnt work like you think it would (your way of doing this would fall apart if dmz doesnt exist in that variable, you'll get a compilation error, similarly if you add another key to the object, say applicationgateway it will work, but that virtual network won't get created)

Related

How do I specify multiple rows in an Azure Vulnerability Assessment baseline definition in my ARM (JSON) template?

I'm trying to add some Azure Vulnerability Assessment baseline definitions to my ARM templates. I use JSON for my ARM templates. I cannot find any documentation on how to specify certain VA baseline definitions, though, namely ones that need to have multiple rows in the baselines.
Specifically, I'm trying to add a baseline defintiion for VA2109. I can locate the documentation for how to define a baseline VA entry in a general sense, which is here...
https://learn.microsoft.com/en-us/azure/templates/microsoft.sql/servers/databases/vulnerabilityassessments/rules/baselines?tabs=json
And then I can locate the description of VA2109 in here ...
https://learn.microsoft.com/en-us/azure/azure-sql/database/sql-database-vulnerability-assessment-rules#authentication-and-authorization
But neither of those tell me how to include more than one user-role mapping. For example, below is what I currently have, which works and lets me specify that a user should have data writer role. But, I also want to specify that the user should have data reader and ddl admin roles.
{
"type": "Microsoft.Sql/servers/databases/vulnerabilityAssessments/rules/baselines",
"apiVersion": "2021-02-01-preview",
"name": "[concat(variables('sqlServerName'), '/', variables('databaseName'), '/default/VA2109/Default')]",
"dependsOn": [
"[resourceId('Microsoft.Sql/servers/databases', variables('sqlServerName'), variables('databaseName'))]"
],
"properties": {
"baselineResults": [
{
"result": ["wibuser", "db_datawriter"]
}
]
}
}
I was able to find an example of what I want using PowerShell. In PowerShell, you can just provide and array of arrays. The PowerShell example can be found here ...
https://learn.microsoft.com/en-us/powershell/module/sqlserver/new-sqlvulnerabilityassessmentbaseline?view=sqlserver-ps#example-2--create-a-new-security-check-baseline-manually
So I adjusted my ARM to do the same thing, but it throws an error saying invalid ARM template. The adjusted ARM I tried looks like below ...
{
"type": "Microsoft.Sql/servers/databases/vulnerabilityAssessments/rules/baselines",
"apiVersion": "2021-02-01-preview",
"name": "[concat(variables('sqlServerName'), '/', variables('databaseName'), '/default/VA2109/Default')]",
"dependsOn": [
"[resourceId('Microsoft.Sql/servers/databases', variables('sqlServerName'), variables('databaseName'))]"
],
"properties": {
"baselineResults": [
{
"result": [
["wibuser", "db_datawriter"],
["wibuser", "db_datareader"]
]
}
]
}
}
Does anybody know how to specify multiple rows in a VA baseline resource when using ARM JSON? Or perhaps know where to find documentation for all of these VA definitions?
Note that baselineResults is an array of rows.
You will need to add each row as an JSON object to that array.
Also, note that each result row should include all columns so you should also include "Principal Type" and "Authentication Type" rows.
It should look something like that:
{
"type": "Microsoft.Sql/servers/databases/vulnerabilityAssessments/rules/baselines",
"apiVersion": "2021-02-01-preview",
"name": "[concat(variables('sqlServerName'), '/', variables('databaseName'), '/default/VA2109/Default')]",
"dependsOn": [
"[resourceId('Microsoft.Sql/servers/databases', variables('sqlServerName'), variables('databaseName'))]"
],
"properties": {
"baselineResults": [
{
"result": ["wibuser", "db_datawriter", "SQL_USER", "NONE"]
},
{
"result": ["wibuser", "db_datareader", "SQL_USER", "NONE"]
}
]
}
}
I added dummy values for "Principal Type" and "Authentication Type" rows, fill your own

Multiple Vms Arm template with different Vm names,sizes and same custom

I have seen many templates to create multi vms as loop using copy function. Ex: vm1, vm2 etc. But this is not how we put in practice as each vm has different function and the naming convention doesn't help.
I am trying to create a template with different vM names, sizes and a single custom Image.
Can anyone please help?
I suggest using an array of name/value pairs, either in the parameters or variables section of your template, for example,
"parameters": {
"vms": {
"type": "array",
"defaultValue": [
{
"name": "vm1",
"size": "Standard_DS1_v2"
},
{
"name": "vm2",
"size": "Standard_A1_v2"
}
]
}
}
Then you can dereference the array with
"copy": {
"name": "vmCopy",
"count": "[length(parameters('vms'))]"
}
and
parameters('vms')[copyIndex()].name
parameters('vms')[copyIndex()].size
By using parameters, we can differentiate the VM names, sizes etc:
"parameters": {
"org": {
"type": "array",
"defaultValue": [
"contoso",
"fabrikam",
"coho"
]
}
},
<![endif]-->
As Copy functions can get the correct value from parameter array and can set the count with length() automatically.
Refer to MS Docs to understand more.
Also have a look on this answer, thanks to SamaraSoucy for explanation.

Nested Copy on Azure ARM template

What I need to do is:
Create routing rules for each of my routes (http) assigned with just 2 frontends
Create one more routing rule, but with all available frontends assigned to it.
Currently I am using the copy like this
copy[
"name": "routingRules",
"count": mylengthvariable,
"input": {
"name": mynamearraywithsomeconcats,
"properties": {
"frontendEndpoints": the frontendpoint for this rule,
etc.. etcc
}
}
}
}
]
this is all well and good but I need to add yet another routing rule, but with more frontendpoints; which would like this: (this is placed outside of the copy above)
"name": "routingRules",
"input": {
"name": "extrarule",
"properties": {
"copy":[
{
"name" : "frontEndpoints",
"count": frontendpointcount,  
"input" : {
"id": a list of frontendpoints
}         
}
],
etc.. etc..
}
},
When I try this, I get the error because I am trying to add one more rule (I think)
I am seeking help on how to implement such a scenario.
Thanks in advance.
It is not possible to perform nested copies in ARM templates (unfortunately). There are some workarounds you can perform, such as expanding the sub-item in a variable, then referencing the variable.
Details and examples can be found here: https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/copy-resources

Azure CORS Variable Allowed Origins

Whenever a new release pipeline is ran in Azure DevOps, the URL Is changed.. currently my ARM template has a hard-coded URL which can be annoying to keep on adding in manually.
"cors": {
"allowedOrigins": [
"[concat('https://',parameters('storage_account_name'),'.z10.web.core.windows.net')]"
}
The only thing that changes is the 10 part in the z10 so essentially i want it to be something like
[concat('https://',parameters('storage_account_name'),'.z', '*', '.web.core.windows.net')] I dont know if something like that is valid but essentially its so that the cors policy will accept the URL regardless of the z number.
Basically speaking this is not possible, because of the CORS standard (see docs).
which allows only for exact origins, wildcard, or null.
For instance, ARM for Azure Storage is also following this pattern allowing you to put a list of exact origins or a wildcard (see ARM docs)
However, if you know your website name, in your ARM you can receive the full host and use it in your CORS:
"[reference(resourceId('Microsoft.Web/sites', parameters('SiteName')), '2018-02-01').defaultHostName]"
The same with a static website (which is your case I guess) if you know the storage account name:
"[reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2019-06-01', 'Full').properties.primaryEndpoints.web]"
Advance reference output manipulation
Answering on comment - if you would like to replace some characters in the output from the reference function the easiest way is to use build-in replace function (see docs)
In case you need a more advanced scenario I am pasting my solution by introducing a custom function which is removing https:// and / from the end so https://contonso.com/ is transformed to contonso.com:
"functions": [
{
"namespace": "lmc",
"members": {
"replaceUri": {
"parameters": [
{
"name": "uriString",
"type": "string"
}
],
"output": {
"type": "string",
"value": "[replace(replace(parameters('uriString'), 'https://',''), '/','')]"
}
}
}
}
],
# ...(some code)...
"resources": [
# ... (some resource)...:
"properties": {
"hostName": "[lmc.replaceUri(reference(variables('storageNameCdn')).primaryEndpoints.blob)]"
}
]

Azure: Cannot use output from Linked Template in IF condition

I'm trying to deploy a Virtual network using ARM template with multiple subnets. I have a linked template which creates NSGs,route table and assigns it to a specific subnet. I'm using copy to create multiple subnets. Route table should be assigned to only one particular subnet. I control this using If condition. The issue here is i'm not able to use the linked template output in the If condition. It fails with below error.
'{
"error": {
"code": "InvalidTemplate",
"message": "Unable to process template language expressions for resource
'/subscriptions//resourceGroups//providers/Microsoft.Network/virtualNetworks/' at line '143' and column '9'. 'The provided
arguments for template language function 'if' is not valid: all arguments should be of type 'boolean'. Please see https://aka.ms/arm-template-expressions#if for usage details.'"
}
}'
Main Template (Subnet Creation snippet):-
"copy": [
{
"name": "subnets",
"count": "[length(parameters('subnetList'))]",
"input": {
"name": "[parameters('subnetList')[copyIndex('subnets')].name]",
"properties": {
"addressPrefix": "[parameters('subnetList')[copyIndex('subnets')].addressprefix]",
"networkSecurityGroup": "[if(equals(parameters('subnetList')[copyIndex('subnets')].name, 'GatewaySubnet'), json('null'), variables('nsgId'))]",
"routeTable": "[if(bool(parameters('subnetList')[copyIndex('subnets')].useRouteTable), reference('routeTableDeployment').outputs.resourceID.value, json('null'))]"
}
}
}
]
Route Table (Output snippet):-
"outputs": {
"resourceID": {
"type": "object",
"value": {
"id": "[resourceId('Microsoft.Network/routeTables', variables('routeTableName')]"
}
}
}
It works if I define a variable and pass it to if condition similar to nsg as below:-
"routeTableId": {
"id": "[resourceId('Microsoft.Network/routeTables', 'routeTableName')]"
}
"routeTable": "[if(bool(parameters('subnetList')[copyIndex('subnets')].useRouteTable), variables('routeTableId'), json('null'))]"

Resources