Master ARM Template
{ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "environment": { "type": "string", "allowedValues": [ "dev", "test", "pprd", "prod" ] }, "sasToken": { "type": "string" } }, "variables": { "baseTemplateUrl": "[concat('https://sa0', parameters('environment'), '.blob.core.windows.net/templates/')]", "linkedTemplates": [ { "name": "web-template", "templateUrl": "[concat(variables('baseTemplateUrl'), 'app-deployment.json')]", "parameterUrl": "[concat(variables('baseTemplateUrl'), 'app-deployment-web.params.json')]" }, { "name": "sql-template", "templateUrl": "[concat(variables('baseTemplateUrl'), 'sql-deployment.json')]", "parameterUrl": "[concat(variables('baseTemplateUrl'), 'sql-deployment.params.json')]" } ] }, "resources": [ { "apiVersion": "2015-01-01", "name": "[concat('nestedTemplate-', variables('linkedTemplates')[copyIndex()].name)]", "type": "Microsoft.Resources/deployments", "copy": { "name": "templatesCopy", "count": "[length(variables('linkedTemplates'))]" }, "properties": { "mode": "Incremental", "templateLink": { "uri": "[concat(variables('linkedTemplates')[copyIndex()].templateUrl, parameters('sasToken'))]", "contentVersion": "1.0.0.0" }, "parametersLink": { "uri": "[concat(variables('linkedTemplates')[copyIndex()].parameterUrl, parameters('sasToken'))]", "contentVersion": "1.0.0.0" }, "parameters": { "environment": { "value": "[parameters('environment')]" } } } } ] }
Each nested template has a parameter called environment
that has the same value as the one in the master template. Each template also has a corresponding parameter file. Therefore, as above, we use both parametersLink
and parameters
properties to handle both. However, life is not easy.
The Problem
We can’t use both parametersLink
and parameters
at the same time. We have to use one or the other. This is by design at the time of this writing. Because the environment
parameter is a common denominator across all the nested templates, it is natural to think that the parameter can be passed from master to nested ones.
How can we work this out then? There are several workarounds.
- The easiest way: Add the common parameters to each parameter JSON file and upload it to Azure Storage Account programmatically.
- Create a set of parameter files for all possible combinations.
- Use something to refer during the template deployment, whose value will be changing over time during the deployment but the reference point remains the same.
Update Parameters Programatically
The easiest way is the first option. It can be simply achieved by running a separate PowerShell script before the deployment. Let’s have a look.
$projectName = "[TEMPLATE_PROJECT_NAME]" $resourceGroupName = "[RESOURCE_GROUP_NAME]" $storageAccountName = "[STORAGE_ACCOUNT_NAME]" $containerName = "[CONTAINER_NAME]" Set-AzureRmCurrentStorageAccount -ResourceGroupName $resourceGroupName -Name $storageAccountName $templates = Get-ChildItem .src$projectNameTemplates*.json -Exclude ("base-*.json", "master-*.json", "*.params.json") foreach($template in $templates) { $filePath = $template.FullName Set-AzureStorageBlobContent -Container $containerName -File $filePath -Force }
This is merely to upload nested template files to Azure Storage Account. It excludes parameter files because they need to be updated before being uploaded. Let’s have a look at the following PowerShell script.
$projectName = "[TEMPLATE_PROJECT_NAME]" $resourceGroupName = "[RESOURCE_GROUP_NAME]" $storageAccountName = "[STORAGE_ACCOUNT_NAME]" $containerName = "[CONTAINER_NAME]" $segments = $resourceGroupName.Split("-") $envName = $segments[$segments.Count - 1] Set-AzureRmCurrentStorageAccount -ResourceGroupName $resourceGroupName -Name $storageAccountName $templates = Get-ChildItem .src$projectNameTemplates*.params.json -Exclude ("base-*.json", "master-*.json") foreach($template in $templates) { $filePath = $template.FullName $json = Get-Content -Path $filePath | ConvertFrom-Json $json.parameters | Add-Member -MemberType NoteProperty -Name environment -Value @{ value = $envName } $json | ConvertTo-Json -Depth 999 | Out-File -FilePath $filePath -Encoding utf8 Set-AzureStorageBlobContent -Container $containerName -File $filePath -Force }
The core part of the script is:
- To read parameter file as a JSON object,
- To add the
environment
property to the JSON object, and - To overwrite the parameter file.
Then this updated parameter files are uploaded to Azure Storage Account. Now our Storage Account has got all nested templates and their parameter files. Let’s run the master template again without the parameters
property.
Tada! It all works fine!
So far, we’ve taken a look how to sort out the restriction that we can’t use both parametersLink
and parameters
properties at the same time. We obviously need a help of another script to run linked templates with their parameters by updating it. It’s definitely not an ideal scenario but certainly it works. If this update is done by hand, it’s tedious, which should be avoided. However, in CI/CD pipelines, it wouldn’t be an issue because we can automate it.
Credit: Github Project