How to dynamically create Azure KeyVault reference in ARM template? - azure

I'm using the following piece of code in my ARM template parameters file to retrieve the secret value from keyvault:
"parameters": {
"mailAccount": {
"reference": {
"keyVault": {
"id": "/subscriptions/GUID/resourceGroups/KeyVaultRG/providers/Microsoft.KeyVault/vaults/KeyVault"
},
"secretName": "mailAccount"
}
},
and in the template file:
"appSettings": [
{
"name": "mailAccount",
"value": "[parameters('mailAccount')]"
},
{
I'd like to know if it is possible to reference a KeyVault by its name using dynamically constructed object (i.e. not /subscriptions/GUID/resourceGroups/KeyVaultRG/providers/Microsoft.KeyVault/vaults/KeyVault but [resourceId(subscription().subscriptionId, resourcegroup().name, 'Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]) or [resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))] ?
In fact, the main objective is to be able to pass the different KeyVault names when deploying templates - where the similar values are stored.
The need to have several KeyVaults is justified by the resources (and cost) separation.
Now I see only validation errors saying ~ resourceId function cannot be used while referencing parameters.
I cannot use nested\linked templates (and output values).

What I am usually doing to avoid this limitation of the resourceId function is to define a variable with the value of the parameter, then using the variable instead in the resourceId function.
Example:
"parameters": {
"KeyVaultName": {
"type": "string",
"metadata": {
"description": "Key Vault Name"
}
}
},
"variables": {
"KeyVaultName": "[parameters('KeyVaultName')]"
}
Then when I am referencing the KeyVault resource I reference it using the variable like this:
"[resourceId('Microsoft.KeyVault/vaults', variables('KeyVaultName')]"

Related

How to reference Keyvault Secret Tags from ARM template

I have an ARM template which syncs secret value from source Keyvault into Destination one.
I also want to sync secret tags, but ARM reference that I use for 'sourceKV.secret.tags' retrieval does not work
[reference(resourceId('subscriptionId', 'resourceGroup', 'Microsoft.KeyVault/vaults/secrets', 'SourceKV', 'Secret'), '2021-04-01-preview', 'Full').tags.tagName]
any ideas what can be the issue, or what is the correct form to retrieve tags during ARM template deployment?
These work for me:
"outputs": {
"tags": {
"type": "string",
"value": "[reference('/subscriptions/xxxx/resourceGroups/yyyy/providers/Microsoft.KeyVault/vaults/zzzz/secrets/mysecret', '2022-07-01', 'Full').tags]"
},
"tagValue": {
"type": "string",
"value": "[reference('/subscriptions/xxxx/resourceGroups/yyyy/providers/Microsoft.KeyVault/vaults/zzzz/secrets/mysecret', '2022-07-01', 'Full').tags.hello]"
},
"tagValue2": {
"type": "string",
"value": "[reference(resourceId(subscription().subscriptionId, resourceGroup().name, 'Microsoft.KeyVault/vaults/secrets', 'xxxx', 'mysecret'), '2021-04-01-preview', 'Full').tags.hello]"
}
}
Will result in:
"outputs": {
"tagValue": {
"type": "String",
"value": "world"
},
"tagValue2": {
"type": "String",
"value": "world"
},
"tags": {
"type": "Object",
"value": {
"hello": "world"
}
}
}
Also works with the API version you used. It is important that you use 'Full', otherwise you won't get the tags. Note that you can use this syntax anywhere in your template. I just used it in the outputs because it is good for testing.
As I found out it is not possible to use Reference function for setting tags property value for keyvault as valid usages state
reference func only works if it is used inside properties block or for outputs; but as tags are not part of properties instead of returning value reference fun returns just string "reference(resource...)"

Nested Parameter in ARM Template

Is there any way to link nested template for parameters in azure ARM templates?
"variables": {
"templatelinkappinsight": "https://xxxxx.blob.net/appinsights.json"
},
For example: I have parameter section defined like following, i want to do nested template linking like variables section as mention above for parameters.
"parameters": {
"functionAppName": {
"value": "test"
},
"applicationInsightsName": {
"value": "test"
},
}

How to check the resource exists in the arm template

How do i identify the azure resource is exists or not in the ARM templates by the resource type and identifier
It is actually kind of possible. You can use resource group tags to mark a current deployed version and skip deployment if the tag is set. All this could be achieved via linked template.
Note that we don't check for resource existence per se but we still allow writing ARM template that could contain one time initialization templates. The last will restore the resource if resource group was deleted and resources were lost (given that you created the resource group again). You can extend this to support per-resource tags which will be more useful in some cases.
The template that starts the deployment may look like this:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"DeploymentTemplateLink": {
"type": "string"
},
"DeploymentVersion": {
"defaultValue": 1,
"type": "int"
}
},
"variables": {
"rgWithDefaultVersion": {
"tags": {
"Version": "0"
}
}
},
"resources": [
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2017-05-10",
"name": "DeploymentTemplate",
"condition": "[less(int(union(variables('rgWithDefaultVersion'), resourceGroup()).tags['Version']), parameters('DeploymentVersion'))]",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[parameters('DeploymentTemplateLink')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"DeploymentVersion": {
"value": "[parameters('DeploymentVersion')]"
}
}
}
}
]
}
The linked template's condition looks into tags and returns true only if current version (stored in the tag) is less than the requested one. You don't actually have to maintain versioning: just don't set the DeploymentVersion parameter and it will deploy only for the first time. If you decide to redeploy anyway you have always an option to increase the version, which will cause deployment of the linked template (aka "main deployment").
The main deployment template is on you, but it should contain a tags resource in order to maintain the logic.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"DeploymentVersion": {
"defaultValue": 1,
"type": "int"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.Resources/tags",
"name": "default",
"apiVersion": "2019-10-01",
"dependsOn": [],
"properties": {
"tags": {
"Version": "[string(parameters('DeploymentVersion'))]"
}
}
}
]
}
Remark for those who didn't understand the union() and rgWithDefaultVersion thing. ARM template deployment will fail if referenced object doesn't contain a property. In our case we have two such properties: 'tags' and 'Version'. 'Tags' will exist only if particular resource group has or ever had tags. 'Version' will exist only after we already wrote it once (in the main deployment). Therefore before we access them we perform union() operation on returned object with a proper default one, ensuring that we can safely access the mentioned properties.
there is no way of doing that in an arm template. you can use some external source (like powershell) to determine that and pass in parameter with appropriate value, alternatively you can use tags to figure that out (have a tag that represents an existence\absence of a resource).
Resource Manager provides the following functions for getting resource values: Resource functions for Azure Resource Manager templates
You could wrap your template with a piece of powershell\whatever, that would determine if the resource exists, and pass in the parameter value depending on that and use a conditional statement in the template that would decide what to do based on the input (but the input has to come from elsewhere)
I needed a solution to this recently to basically do an incremental update to a SQL server. Since you can't do this; the template will fail with a NameAlreadyExists error.
So I needed to check the resource doesn't exist and only create if it doesn't.
Add a "condition" check for the azure resource id exists; don't create if it does.
{
...
"condition": "[empty(resourceId('[resourceGroup().id]', 'Microsoft.SQL/servers', parameters('serverName')))]",
...
}
You can do this for any resource type.

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'))]"

How to Copy Azure SQL Database using ARM Template

Not sure if it is supported in ARM. I could find power-shell references only.
You cannot currently deploy a dacpac with an ARM template. The link above uses PowerShell but not ARM. You can create however create a database from a source database as a copy using an ARM template.
A simple way to find an example template for any Azure action is to perform the action in the portal - in this case, copy a database - and then open the appropriate resource group blade in the portal, list the deployments, locate the deployment just submitted and open it. Then select ViewTemplate from the menu bar and examine both the Template tab and the Parameters tab. These show you the full template and the parameter values actually used. You can then download the template, with accompanying Powershell script.
For database copy, here is the template:
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"databaseName": {
"type": "string"
},
"serverName": {
"type": "string"
},
"location": {
"type": "string"
},
"createMode": {
"type": "string"
},
"sourceDatabaseId": {
"type": "string"
},
"requestedServiceObjectiveName": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "2014-04-01-preview",
"location": "[parameters('location')]",
"name": "[concat(parameters('serverName'), '/', parameters('databaseName'))]",
"properties": {
"createMode": "[parameters('createMode')]",
"sourceDatabaseId": "[parameters('sourceDatabaseId')]",
"requestedServiceObjectiveName": "[parameters('requestedServiceObjectiveName')]"
},
"type": "Microsoft.Sql/servers/databases"
}
]
}
For database copy createMode = 'Copy'
And be sure to provide a fully qualified resourceId formatted as follows:
"/subscriptions/<sub-id>/resourceGroups/<resourceGroupName>/providers/Microsoft.Sql/Servers/<server-name>/databases/<database-name>"
Make sure the resource group name capitalization is correct and that the server name is all lower case.
You can use the sourceDatabaseId property to reference another database. Then you can specify various createModes depending on what type of database you would like to create:
{
"properties": {
"createMode": "OnlineSecondary",
"sourceDatabaseId": "[resourceId('Microsoft.Sql/servers/databases', variables('sql01Name'), 'databasename')]"
}
}
http://msdn.microsoft.com/en-us/library/azure/mt163685.aspx
The answer above from #Bill Gibson - MSFT works if you are using a Microsoft.Sql/servers resource, however if you're using a Microsoft.Sql/managedInstances resource you'll need to use the appropriate Microsoft.Sql/managedInstance/databases - ARM Template.
The following works for me to perform a PointInTimeRestore accessing a source database that lives in another resource group (the variables and parameters are left as an exercise to the reader):
{
"type": "Microsoft.Sql/managedInstances/databases",
"name": "[concat(variables('destinationSqlManagedInstanceName'), '/', 'AdventureWorks')]",
"apiVersion": "2021-11-01",
"location": "[parameters('location')]",
"properties": {
"createMode": "PointInTimeRestore",
"restorePointInTime": "2022-12-14T12:00:00Z",
"sourceDatabaseId": "[resourceId(variables('sourceResourceGroupName'), 'Microsoft.Sql/managedInstances/databases', variables('sourceSqlManagedInstanceName'), 'AdventureWorks')]"
}
}
The documentation is broken in a few ways:
When attempting to perform a PointInTimeRestore the properties referenced (SourceDatabaseName, SourceManagedInstanceName, PointInTime) do not exist. Rather the following properties are used: restorePointInTime and sourceDatabaseId which are documented in the documentation.
Additionally, the restorePointInTime indicates that the time should be in ISO8601 format, however this is not the same as what is returned by utcNow(). Testing has shown that you must provide it in this version of the ISO8601 format: yyyy-MM-ddTHH:mm:ssZ which can be done using utcNow('yyyy-MM-ddTHH:mm:ssZ').
I have created an issue to try and get the documentation fixed up here: https://github.com/MicrosoftDocs/azure-docs/issues/102717

Resources