Azure Deployment - Overide parameter with ARM functions - azure

I'm using VSTS to deploy azure resources.
I use task "Azure Resource Group Deployment" to deploy ARM templates.
How can I, for a specific parameter, override the value with a ARM function (concat, listkeys, etc)?
Example: My ARM template has a parameter that is a storage account key and instead of providing the key directly, I want to provide it by passing [listkeys(...)]

You cannot do that, several functions (like listKeys()) are evaluated at runtime only. I don't know what you are trying to achieve, so there are probably ways of doing what you try to achieve.
If you want to hide the keys you can store them in the Key Vault and retrieve at deployment time:
"password": {
"reference": {
"keyVault": {
"id": "[resourceId('kvGroup', 'Microsoft.KeyVault/vaults', 'kvName')]"
},
"secretName": "secret"
}
},

If the storage account isn't created within the same ARM template, I'd use the parameter to supply the name of the storage account and then listkeys() within the ARM template to get at the storage account connection string.
If you're creating the storage account in a previous ARM template deployment in your pipeline you could use output parameters to make the connection string available in the pipeline. Here is an example where xxx represents your company naming prefix:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environment": {
"type": "string",
"defaultValue": "d",
"metadata": {
"description": "The deployment environment, given by develop (d), testing (t), production (p) or quality assurance (q)"
}
}
},
"variables": {
"busUnit": "vendor_name_here",
//storage account names must be lowercase and are limited to 24 alpha numeric characters
"storage_account_name": "[concat('xxx', parameters('environment'), variables('busUnit'), 'stor')]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"sku": {
"name": "Standard_LRS", //this is a hard coded SKU
"tier": "Standard" //general purpose versus blob-only
},
"kind": "Storage",
"name": "[variables('storage_account_name')]",
"apiVersion": "2017-06-01",
"location": "[resourceGroup().location]", //add it to the same region/location as the resource group
"properties": {
"encryption": {
"keySource": "Microsoft.Storage",
"services": {
"blob": {
"enabled": true
}
}
},
"networkAcls": {
"bypass": "AzureServices",
"defaultAction": "Allow",
"ipRules": [],
"virtualNetworkRules": []
}
},
"dependsOn": []
}
],
"outputs": {
"storageAccountKey": {
//"description": "This works if the storage account is in the same resource group. It returns the access key for the account",
"type": "securestring",
"value": "[listKeys(variables('storage_account_name'),'2015-05-01-preview').key1]"
},
"storageAccountName": {
//"description": "This is the computed name of the storage account, based on naming conventions in the variables",
"type": "string",
"value": "[variables('storage_account_name')]"
}
}
}

Related

How to use conditional OR in arm template?

I'm trying to create a private endpoint through an ARM template for a storage account if the storage account SKU is Standard_GRS or Standard_RAGRS, or Standard_GZRS. How to include this in the conditional statement in the ARM template.
We have tested this in our local environment, below statements are based on our analysis.
In our local environment, we have created an ARM template to deploy storage account a condition the SKU of the storage account should be either of the below :
"Standard_GRS", "Standard_RAGRS", "Standard_GZRS"
To achieve this we have used the below condition in our ARM template :
"condition":"[or(equals(parameters('sku'),'Standard_RAGRS'),equals(parameters('sku'),'Standard_GZRS'),equals(parameters('sku'),'Standard_GRS'))]",
Here is the ARM template that we have used:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"sku": {
"type": "string"
}
},
"functions": [],
"variables": {
},
"resources": [
{
"condition":"[or(equals(parameters('sku'),'Standard_RAGRS'),equals(parameters('sku'),'Standard_GZRS'),equals(parameters('sku'),'Standard_GRS'))]",
"name": "<strgaccount>",
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"tags": {
"displayName": "storageaccount1"
},
"location": "[resourceGroup().location]",
"kind": "StorageV2",
"sku": {
"name": "[parameters('sku')]"
}
}
],
"outputs": {}
}
Here is the sample output for reference:
In the below Output we have passed the SKU value "Standard_GZRS" the condition got succeeded and Resource got deployed.
In the below Output we have passed the SKU value "Standard_LRS" the condition got failed & the resource didn't get deployed.

How to pass DevOps release secrets to ARM for Key Vault Deployment

I am trying to deploy a key vault with secretName and secretValue and I have created a variable group in azure devops with all the secrets and I am using the below parameters in parameter file, but when this gets deployed the secret value gets stored as $(secret) and not the password actually stored in the task group in Azure DevOps.
"secretsObject": {
"value": {
"secrets": [
{
"secretName": "App012",
"secretValue": "$(mysecret)"
},
and this is what I got in the key vault template:
{
"type": "Microsoft.KeyVault/vaults/secrets",
"name": "[concat(parameters('keyVaultName'), '/', parameters('secretsObject').secrets[copyIndex()].secretName)]",
"apiVersion": "2018-02-14",
"dependsOn": [
"[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]"
],
"copy": {
"name": "secretsCopy",
"count": "[length(parameters('secretsObject').secrets)]"
},
"properties": {
"value": "[parameters('secretsObject').secrets[copyIndex()].secretValue]"
}
}
]
}
Any idea how to pass the "secretvalue" as a variable?
I believe your asking how to leverage your secrets that are stored as a variable group to be deployed securely with your ARM template via Azure DevOps. If that is the case look at using Override Template Parameters in your release task.
This would be in the format of -NameOfARMParameter $(NameofDevOpsVariable)
In your case it would be -mysecret $(NameOfDevOpsVariable)
The deployment .json should look like this for parameter declaration:
"secretValue": {
"type": "string",
"metadata": {
"description": "This is for receiving a value from DevOps releases of the secret to be stored in the key vault"
}
},
"secretName": {
"type": "string",
"metadata": {
"description": "Name of the Secret"
}
},
For the actual deployment
{
"type": "Microsoft.KeyVault/vaults/secrets",
"name": "[concat(variables('keyVaultName'),'/',parameters('secretName'))]",
"apiVersion": "2018-02-14",
"properties": {
"contentType": "text/plain",
"value": "[parameters('secretValue')]"
},
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))]"
]
},
And the parameters file doesn't need to have anything in it if these values will be fed from Dev Ops
You need to create a parameter file with the secret / link to Key Vault.
Here's a sample of it:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"adminLogin": {
"value": "exampleadmin"
},
"adminPassword": {
"reference": {
"keyVault": {
"id": "/subscriptions/<subscription-id>/resourceGroups/<rg-name>/providers/Microsoft.KeyVault/vaults/<vault-name>"
},
"secretName": "ExamplePassword"
}
},
"sqlServerName": {
"value": "<your-server-name>"
}
}
}
More info:
https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/parameter-files
and https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/add-template-to-azure-pipelines

How to listkeys on a storage account deployed via linked ARM template?

Below I have a (simplified) Azure ARM Template to deploy a website which has in its appSettings the storage account. I originally passed the key via a string output parameter which works fine.
Storage Template
"outputs": {
"storageKey": {
"type": "string",
"value": "[listKeys(resourceid(resourceGroup().name, 'Microsoft.Storage/storageAccounts', parameters('storageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value]"
}
}
Root template
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Resources/deployments",
"name": "[concat(resourceGroup().Name, '-', variables('tdfConfiguration')[copyIndex()]['roleName'], '-storage')]",
"copy": {
"name": "storageCopy",
"count": "[length(variables('tdfConfiguration'))]"
},
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('storageAccountTemplateUri')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"storageAccountName": { "value": "[variables('tdfConfiguration')[copyIndex()]['componentName']]" },
"storageAccountLocation": { "value": "[resourceGroup().location]" },
"storageAccountType": { "value": "[variables('storageAccountType')]" }
}
}
},
{
"apiVersion": "[variables('apiVersion')]",
"type": "Microsoft.Resources/deployments",
"name": "[concat(resourceGroup().Name, '-', variables('tdfConfiguration')[copyIndex()]['roleName'], '-website')]",
"copy": {
"name": "webSiteCopy",
"count": "[length(variables('tdfConfiguration'))]"
},
"dependsOn": [
"[concat('Microsoft.Resources/deployments/', resourceGroup().Name, '-', variables('tdfConfiguration')[copyIndex()]['roleName'], '-serviceplan')]",
"[concat('Microsoft.Resources/deployments/', resourceGroup().Name, '-', variables('tdfConfiguration')[copyIndex()]['roleName'], '-storage')]"
],
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('webSiteTemplateUri')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"azureWebJobsStorageAccountKey": { "value": "[reference(concat(resourceGroup().Name, '-', variables('tdfConfiguration')[copyIndex()]['roleName'], '-storage')).outputs.storageKey.value]" }
}
}
},
I'm worried that passing that around as a string may expose it in some deployment logs. However if I switch it to a securestring output parameter I can no longer access the value. So it seems like I need to listkeys in the root template, but if I change my website parameters to
"azureWebJobsStorageAccountKey": { "value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts',variables('tdfConfiguration')[copyIndex()]['componentName']), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value]" }
it fails because even though the storage account is listed as a dependsOn, it attempts to resolve this value immediately without waiting for the storage account to be deployed. Any ideas?
list* functions will wait for the resource to be available if it is being created in the same template. but you are using nested templates so it has no way of knowing the resource is provisioned or not, so it just assumes its provisioned (standard behavior when you use list* functions).
dont pass it in as a value (it makes no sense really), just use the same expression inside the deployment and it will work. because the deployment will start only after the previous deployment was finished and the storage account will be there already.
Also, I dont see why you are doing this with nested templates, I dont see any reason for doing this in your case, you are over complicating your deployment\code for no benefit (and even created a problem for yourself because of that). Just drop nested deployments and use resources.

Is it possible to create an Azure Blob Storage Connector using a ARM template or a script?

I'm creating a logic app which will do some operations on a blob storage, thus it needs a Connector to a specific blob storage. I'm able to define which Connector should be used (providing its name and other properties), however if it doesn't exist yet, the template fails to deploy. I know we can create these connectors via logic app designer, but i would very much like to automate that process. Hence the question:
Is it possible to deploy/create this connector using an ARM template or a script?
You can check this post related to Logic App connector.
Here is an ARM Template that create an API connection to blob storage:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"azureBlobConnectionAPIName": {
"type": "string",
"metadata": {
"description": "The name of the connection api to access the azure blob storage."
}
},
"storageAccountName": {
"type": "string",
"metadata": {
"description": "The Storage Account Name."
}
}
},
"variables": {
"storageAccountId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
},
"resources": [
{
"type": "Microsoft.Web/connections",
"name": "[parameters('azureBlobConnectionAPIName')]",
"apiVersion": "2016-06-01",
"location": "[resourceGroup().location]",
"scale": null,
"properties": {
"displayName": "[parameters('azureBlobConnectionAPIName')]",
"parameterValues": {
"accountName": "[parameters('storageAccountName')]",
"accessKey": "[listKeys(variables('storageAccountId'),'2015-05-01-preview').key1]"
},
"api": {
"id": "[concat('subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', parameters('defaultResourceLocation'), '/managedApis/azureblob')]"
}
},
"dependsOn": []
}
]
}

Retrieve Service Bus event hub connection string

I have an existing Service Bus with one queue and event hub deployed using Azure Resource Manager.
Now I am interested to retrieve the primary key and connection string using Azure PowerShell wiithout using the ServiceBus.dll. Is it possible??
As a workaround I have created an ARM template which does not deploy anything but just query the existing resource and retrieve the information I need. The below template retrieves the connection string and primary key of an event hub/queue for a specific service bus namespace
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"serviceBusNamespace": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "The name of the service bus namespace to create."
}
},
"resourceName": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "The name of the resource to be retreived."
}
},
"resourceType": {
"type": "string",
"minLength": 1,
"allowedValues": [
"queues",
"eventhubs"
],
"metadata": {
"description": "The type of the resource"
}
},
"policy": {
"type": "string",
"minLength": 1,
"defaultValue": "ManagePolicy",
"allowedValues": [
"ManagePolicy",
"SendPolicy",
"ListenPolicy"
],
"metadata": {
"description": "The type of the resource"
}
}
},
"variables": {
},
"resources": [ ],
"outputs": {
"connectionString": {
"type": "string",
"value": "[listKeys(resourceId(concat('Microsoft.ServiceBus/namespaces/',parameters('resourceType'),'/authorizationRules'),parameters('serviceBusNamespace'),parameters('resourceName'),parameters('policy')),'2015-08-01').primaryConnectionString]"
},
"primaryKey": {
"type": "string",
"value": "[listKeys(resourceId(concat('Microsoft.ServiceBus/namespaces/',parameters('resourceType'),'/authorizationRules'),parameters('serviceBusNamespace'),parameters('resourceName'),parameters('policy')),'2015-08-01').primaryKey]"
}
}
}
Is it abusing to use ARM template to query for a resource and not actually deploy anything?
EDIT
To capture the output of the ARM template within PowerShell use the below code
$ep = New-AzureRmResourceGroupDeployment -Name "getEventHub" -ResourceGroupName myResourceGroup -Mode Incremental -TemplateFile getEventHub.json -TemplateParameterFile getEventHub.param.json
$RuleConnString = $ep.Outputs.connectionString.value
$RulePrimaryKey = $ep.Outputs.primaryKey.value
Note that the property names connectionString and primaryKey are same as defined in my template file
EDIT 2
If I re-run the ARM template to deploy the event hub second time I get the below error.
I din't find any option other than to use the ARM template to query the details.
I don’t see what’s wrong with what you’re doing. In my view Resource Manager templates in their nature are incremental. So you could author a template to create your existing service bus with the same resources. If the properties are the same then it will leave the existing resources intact and return you the connection string and primary key of the relevant resource.
I have a need to automate the creation of a service bus and queue and separate send/listen shared access policies. You can retrieve the connection string on the service bus itself using PowerShell natively without using the .Net ServiceBus.dll assembly by using Get-AzureSBAuthorizationRule but due to a still current bug this doesn’t work at the queue level.
I tried using the ServiceBus.dll to create the shared access policies but sometimes it would randomly fail but subsequently work if you ran it a second time immediately afterwards. I also tried Resource Manager templates but previously you had to pass in the keys you’d generated yourself. Now I see Microsoft generate those for you but you’re still left trying to get the key in an automated fashion so I like your solution.
One question though, can you capture the Resource Manager template outputs and pass them back to a PowerShell script, do you know?
Cheers
Rob
{ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": {
"servicebusNamespace": {
"type": "string",
"metadata": {
"description": "The service bus namespace"
}
},
"notificationssmsqueue": {
"type": "string",
"metadata": {
"description": "Notifications SMS queue"
}
} }, "variables": {
"location": "[resourceGroup().location]", }, "resources": [
{
"apiVersion": "2015-08-01",
"name": "[parameters('servicebusNamespace')]",
"type": "Microsoft.ServiceBus/namespaces",
"location": "[variables('location')]",
"properties": {
"messagingSku": 2
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "[parameters('notificationssmsqueue')]",
"type": "Queues",
"dependsOn": [
"[concat('Microsoft.ServiceBus/namespaces/', parameters('servicebusNamespace'))]"
],
"properties": {
"path": "[parameters('notificationssmsqueue')]"
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "[concat(parameters('notificationssmsqueue'),'.listen')]",
"type": "AuthorizationRules",
"dependsOn": [
"[parameters('notificationssmsqueue')]"
],
"properties": {
"keyName": "[concat(parameters('notificationssmsqueue'),'.listen')]",
"claimType": "SharedAccessKey",
"claimValue": "None",
"rights": [ "Listen" ],
"revision": -1
}
},
{
"apiVersion": "2015-08-01",
"name": "[concat(parameters('notificationssmsqueue'),'.send')]",
"type": "AuthorizationRules",
"dependsOn": [
"[parameters('notificationssmsqueue')]"
],
"properties": {
"keyName": "[concat(parameters('notificationssmsqueue'),'.send')]",
"claimType": "SharedAccessKey",
"claimValue": "None",
"rights": [ "Send" ],
"revision": -1
}
}
]
}
]
} ], "outputs": {
"connectionString": {
"type": "string",
"value": "[listKeys(resourceId(concat('Microsoft.ServiceBus/namespaces/AuthorizationRules'),parameters('serviceBusNamespace'),'RootManageSharedAccessKey'),'2015-08-01').primaryConnectionString]"
},
"smsSendPrimaryKey": {
"type": "string",
"value": "[listKeys(resourceId(concat('Microsoft.ServiceBus/namespaces/Queues/AuthorizationRules'),parameters('serviceBusNamespace'),parameters('notificationssmsqueue'),concat(parameters('notificationssmsqueue'),'.send')),'2015-08-01').PrimaryKey]"
},
"smsListenPrimaryKey": {
"type": "string",
"value": "[listKeys(resourceId(concat('Microsoft.ServiceBus/namespaces/Queues/AuthorizationRules'),parameters('serviceBusNamespace'),parameters('notificationssmsqueue'),concat(parameters('notificationssmsqueue'),'.listen')),'2015-08-01').PrimaryKey]"
} } }
But I call my templates like this:
New-AzureRMResourceGroupDeployment -ResourceGroupName $ResourceGroupName -TemplateFile "$scripts_folder$SB_create_script" -TemplateParameterObject `
#{ servicebusNamespace = $servicebusNamespace;
notificationssmsqueue = $NotificationSMSqueue }
This is the correct way to get the information you are seeking. The Resource Manager provides a common interface to interact with all the services. It is how the Portal access the services, and each of the language SDKs are just wrappers for similar requests to the one you have created.
I usually use the Python or java SDKs, but I have been told that NodeJS is a very easy way to wrap the Web APIs that ARM calls to construct similar calls like the one you made, if you are looking for a none ARM way to do this.

Resources