Related
Is there a way to apply RBAC rules at the resource level via ARM? I was able to follow this Microsoft guide to add a user/role at the resource group level, but not at the resource. In particular, I am trying to add a new reader role to AppInsights via ARM. However, when I adjust the scope, the template just fails with this error:
"error": {
"code": "InvalidCreateRoleAssignmentRequest",
"message": "The request to create role assignment '{guid}' is not valid. Role assignment scope '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/Microsoft.Insights/components/{resourceGroupName}' must match the scope specified on the URI '/subscriptions/{resourceGroupName}/resourcegroups/{resourceGroupName}'."
}
I am left wondering what the scope variable is for if it cannot be changed. Is there some other place I should be modifying the scope to get this working?
Thanks in advance!
The key is to drop the scope property, and instead nest the role assignment under the desired resource by using Microsoft.FooResource/BarSubType/providers/roleAssignments as the type, and using the following format for the name: {resourceName}/Microsoft.Authorization/{uniqueRoleAssignmentGuid}. Note that the GUID should be stable but unique to this role assignment, one easy option is guid(subscription().subscriptionId, 'some-sub-identifier-if-you-wish').
Here is a template that shows you how to apply RBAC to a single resource, using a user-assigned managed identity defined in the same template:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": { "type": "string" },
"userAssignedIdentityName": { "type": "string" }
},
"variables": {
"ContributorRoleDefinition": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
},
"resources": [
{
"type": "Microsoft.ManagedIdentity/userAssignedIdentities",
"name": "[parameters('userAssignedIdentityName')]",
"location": "[resourceGroup().location]",
"apiVersion": "2018-11-30"
},
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('storageAccountName')]",
"location": "[resourceGroup().location]",
"apiVersion": "2016-12-01",
"sku": { "name": "Standard_LRS" },
"kind": "Storage",
"resources": [
{
"type": "Microsoft.Storage/storageAccounts/providers/roleAssignments",
"apiVersion": "2017-05-01",
"name": "[concat(parameters('storageAccountName'), '/Microsoft.Authorization/', guid(subscription().subscriptionId, 'foo'))]",
"properties": {
"roleDefinitionId": "[variables('ContributorRoleDefinition')]",
"principalId": "[reference(parameters('userAssignedIdentityName'), '2018-11-30').principalId]"
},
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
"[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName'))]"
]
}
]
}
]
}
Source: https://www.henrybeen.nl/creating-an-authorization-rule-using-an-arm-template/
You apply RBAC rules at the resource level via an ARM and there is example template that applies RBAC rules at Azure VM here:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"principalId": {
"type": "string",
"metadata": {
"description": "Principal ID associated with the subscription ID"
}
},
"virtualMachineName": {
"type": "string",
"metadata": {
"description": "Name of the virtual machine"
}
},
"builtInRoleType": {
"type": "string",
"metadata": {
"description": "Built In Role Type for the Virtual Machine"
},
"allowedValues": [
"Owner",
"Contributor",
"Reader",
"Virtual Machine Contributor"
]
},
"guid": {
"type": "string",
"metadata": {
"description": "A new GUID used to identify the role"
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for all resources."
}
}
},
"variables": {
"Owner": "[concat('/subscriptions/',subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
"Contributor": "[concat('/subscriptions/',subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
"Reader": "[concat('/subscriptions/',subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
"Virtual Machine Contributor": "[concat('/subscriptions/',subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'd73bb868-a0df-4d4d-bd69-98a00b01fccb')]",
"resourceName": "[concat(parameters('virtualMachineName'), '/Microsoft.Authorization/', parameters('guid'))]"
},
"resources": [
{
"type": "Microsoft.Compute/virtualMachines/providers/roleAssignments",
"apiVersion": "2017-05-01",
"name": "[variables('resourceName')]",
"properties": {
"roleDefinitionId": "[variables(parameters('builtInRoleType'))]",
"principalId": "[parameters('principalId')]"
}
}
]
}
Hope this will help you.
Microsoft has finally provided documentation explaining this:
https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-template#resource-scope
It is possible to apply RBAC on resource level using ARM.
The example what you referred shows how to apply RBAC on a particular resource group, where the scope is the path of the resource group.
Here, you are trying to assign a role to a particular resource. Changing the scope from resource group to resource (AppInsights) will work.
From the exception, I can see that the path of the resource may not be in the expected format.
The path of AppInsights should be in the following format,
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/microsoft.insights/components/{insightName}
Hope framing the scope like this helps!
Agree that the documentation on this issue is less than useful. I have an array of Role IDs that I wanted to add as Owners at the App Insight resource, without making the users Owners at the Resource Group level. I didn't want to use the nested resource approach as I wanted to iterate over an array of objects to dynamically create the roles, so after tweaking the type, name, and scope attributes, the following resource block is what ended up working for me:
{
"comments": "Add the Application Insights resource",
"apiVersion": "2014-04-01",
"name": "[variables('appInsightsName')]",
"type": "Microsoft.Insights/components",
"location": "[resourceGroup().location]",
"properties": {
"ApplicationId": "[variables('appInsightsName')]"
}
},
{
"comments": "Add the IAM roles to the App Insights resource",
"condition": "[parameters('isProduction')]",
"type": "Microsoft.Insights/components/providers/roleAssignments",
"name": "[concat(variables('appInsightsName'),'/Microsoft.Authorization/',guid(parameters('roleAssignments')[copyIndex()].principalId))]",
"apiVersion": "2017-05-01",
"location": "[resourceGroup().location]",
"properties": {
"roleDefinitionId": "[concat(subscription().Id, '/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", // Owner Role
"principalId": "[parameters('roleAssignments')[copyIndex()].principalId]",
"scope": "[resourceId('Microsoft.Insights/components', variables('appInsightsName'))]"
},
"copy": {
"name": "appInsightsRoleAssignments",
"count": "[length(parameters('roleAssignments'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Insights/components', variables('appInsightsName'))]"
]
}
I have been trying to automate deployment of my resources to resource group on Azure. Right now I am using ARM templates and so far I was able to create App Insights and App Service Plan using a template. This is how it looks like:
{
"apiVersion": "2015-05-01",
"name": "[variables('servicePlan')]",
"kind": "linux",
"type": "Microsoft.Web/serverfarms",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "BTC Push Notification Settings HostingPlan"
},
"sku": {
"name": "[variables('skuNamePlan')]",
"capacity": "[variables('skuSizePlan')]"
},
"properties": {
"name": "[variables('servicePlan')]"
}
},
{
"apiVersion": "2015-05-01",
"name": "[variables('appInsights')]",
"type": "Microsoft.Insights/components",
"location": "southcentralus",
"tags": {
"[concat('hidden-link:', resourceGroup().id, '/providers/Microsoft.Web/sites/', variables('appInsights'))]": "Resource",
"displayName": "BTC Push Notification Settings App Insights"
},
"properties": {
"applicationId": "[variables('appInsights')]"
}
}
I am having hard time creating Web App for Containers and pointing it to my docker image using ARM template. I have done it manually and it worked, likewise I did it through azure-cli like this az webapp create --resource-group ExampleGroupAlpina --plan myAppServicePlan --name DockerContainer --deployment-container-image-name this-is-my-image/sample-docker and this worked as well. I would appreciate if anyone could suggest creating this Web App for Containers using ARM Template.
For me none of these other answers worked. With the assistance of Azure support and these other answers I came up with the following template, that succesfully creates an app service plan with an Linux App Service for Containers running a custom Docker image from Azure Container Repository:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environment":{
"type": "string"
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location (region) for all resources."
}
},
"appServiceSku": {
"type": "string",
"defaultValue": "B1",
"metadata": {
"description": "The SKU of App Service Plan "
}
},
"dockerImageName": {
"type": "string",
"defaultValue": "_MY_REGISTRY_USERNAME_.azurecr.io/_MY_NAMESPACE_/_MY_DOCKER_IMAGE_NAME_:_TAG_"
},
"dockerRegistryUrl": {
"type": "string",
"defaultValue": "https://_MY_REGISTRY_USERNAME_.azurecr.io"
},
"dockerRegistryUsername": {
"type": "string",
"defaultValue": "_MY_REGISTRY_USERNAME_"
},
"dockerRegistryPassword": {
"type": "string",
"defaultValue": "_MY_REGISTRY_PSW_"
},
"_artifactsLocation": {
"type": "string"
},
"_artifactsLocationSasToken": {
"type": "securestring"
}
},
"variables": {
"name": "projectname-",
"webAppPortalName": "[concat(variables('name'), parameters('environment'), '-webapp')]",
"appServicePlanName": "[concat(variables('name'), parameters('environment'),'-hosting')]",
"resources": [
{
"apiVersion": "2017-08-01",
"type": "Microsoft.Web/serverfarms",
"kind": "linux",
"name": "[variables('appServicePlanName')]",
"location": "[parameters('location')]",
"comments": "This app service plan is used for the web app and slots.",
"properties": {
"reserved": true
},
"dependsOn": [],
"sku": {
"name": "[parameters('appServiceSku')]"
}
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2016-08-01",
"name": "[variables('webAppPortalName')]",
"kind": "app,linux,container",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
],
"properties": {
"name": "[variables('webAppPortalName')]",
"siteConfig": {
"linuxFxVersion": "[concat('DOCKER|', parameters('dockerImageName'))]",
"alwaysOn": true,
"appSettings": [
{
"name": "WEBSITES_ENABLE_APP_SERVICE_STORAGE",
"value": "false"
},
{
"name": "DOCKER_REGISTRY_SERVER_URL",
"value": "[parameters('dockerRegistryUrl')]"
},
{
"name": "DOCKER_REGISTRY_SERVER_USERNAME",
"value": "[parameters('dockerRegistryUsername')]"
},
{
"name": "DOCKER_REGISTRY_SERVER_PASSWORD",
"value": "[parameters('dockerRegistryPassword')]"
}
]
},
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
}
}
]
}
_artifactsLocation and _artifactsLocationSasToken do not need values, but somehow they need to be included. The main difference to other answers is the inclusion of the reserved attribute in the properties for the App Service Plan.
Hopefully this saves some of the headache this created for me!
The following ARM template worked for me.
It allows to specify the authentication details of a private Azure Container Registry.
Also make sure the docker image name follows this pattern: _MY_REGISTRY_USERNAME_-on.azurecr.io/_MY_NAMESPACE_/_MY_DOCKER_IMAGE_NAME_:latest
I run the az command like this:
az group deployment create \
--name "deployAzureApp" \
--resource-group <MY_RESOURCE_GROUP_NAME> \
--template-file <MY_ARM_JSON_TEMPLATE>.json --verbose --debug
Here's the Azure Resource Manager (ARM) JSON template:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appName": {
"type": "String",
"defaultValue": "_MY_APP_NAME_"
},
"dockerImageName": {
"type": "String",
"defaultValue": "_MY_REGISTRY_USERNAME_-on.azurecr.io/_MY_NAMESPACE_/_MY_DOCKER_IMAGE_NAME_:latest"
},
"dockerRegistryUrl": {
"type": "String",
"defaultValue": "https://_MY_REGISTRY_USERNAME_-on.azurecr.io"
},
"dockerRegistryUsername": {
"type": "String",
"defaultValue": "_MY_REGISTRY_USERNAME_"
},
"dockerRegistryPassword": {
"type": "String",
"defaultValue": "_MY_REGISTRY_PSW_"
},
"servicePlanName": {
"type": "String",
"defaultValue": "_MY_SERVICE_PLAN_NAME_"
},
"appLocation": {
"type": "String",
"defaultValue": "_MY_REGION_"
}
},
"resources": [
{
"type": "Microsoft.Web/sites",
"apiVersion": "2016-08-01",
"name": "[parameters('appName')]",
"kind": "app,linux,container",
"location": "[parameters('appLocation')]",
"properties": {
"name": "[parameters('appName')]",
"siteConfig": {
"linuxFxVersion": "[concat('DOCKER|', parameters('dockerImageName'))]",
"alwaysOn": true,
"appSettings": [
{
"name": "WEBSITES_ENABLE_APP_SERVICE_STORAGE",
"value": "false"
},
{
"name": "DOCKER_REGISTRY_SERVER_URL",
"value": "[parameters('dockerRegistryUrl')]"
},
{
"name": "DOCKER_REGISTRY_SERVER_USERNAME",
"value": "[parameters('dockerRegistryUsername')]"
},
{
"name": "DOCKER_REGISTRY_SERVER_PASSWORD",
"value": "[parameters('dockerRegistryPassword')]"
}
]
},
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('servicePlanName'))]"
}
}
]
}
About Azure Web App for Container, actually, there is just one point different with Azure Web App in the template. The point is the kind type.
Azure Web App:
"kind": "app"
Azure Web App for Container:
"kind": "app,linux,container",
So, you can create the Azure Web App for Container using template just setting up the kind with app,linux,container.
Update
I do the test and find out that the website kind is not the most important. The key is the property of the website:
"siteConfig": {
"linuxFxVersion": "DOCKER|nginx"
},
And the template will like below and it does a good job.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"webAppName": {
"type": "string",
"metadata": {
"description": "Base name of the resource such as web app name and app service plan "
},
"minLength": 2
},
"sku": {
"type": "string",
"defaultValue": "S1",
"metadata": {
"description": "The SKU of App Service Plan "
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for all resources."
}
}
},
"variables": {
"webAppPortalName": "[concat(parameters('webAppName'), '-webapp')]",
"appServicePlanName": "[concat('AppServicePlan-', parameters('webAppName'))]"
},
"resources": [
{
"apiVersion": "2017-08-01",
"type": "Microsoft.Web/serverfarms",
"kind": "linux",
"name": "[variables('appServicePlanName')]",
"location": "[parameters('location')]",
"comments": "This app service plan is used for the web app and slots.",
"properties": {},
"dependsOn": [],
"sku": {
"name": "[parameters('sku')]"
}
},
{
"apiVersion": "2016-08-01",
"type": "Microsoft.Web/sites",
"name": "[variables('webAppPortalName')]",
"location": "[parameters('location')]",
"comments": "This is the web app, also the default 'nameless' slot.",
"properties": {
"name": "[parameters('webAppName')]",
"siteConfig": {
"appCommandLine": "",
"linuxFxVersion": "DOCKER|nginx"
},
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
]
}
]
}
For what I observed, the linuxFxVersion problem depends on the App Service Plan where the Web App is hosted. What helped me was to set the reserved property on the properties object of App Service Plan resource to true.
What Microsoft doumentation says about the reserved setting:
If Linux app service plan true, false otherwise.
It says nothing about its default value, but from what I observed its false.
This is how my App Service Plan resource ARM template looks like.
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2019-08-01",
"name": "[variables('appServicePlanName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('appServicePlanSkuName')]",
"tier": "[parameters('appServicePlanSkuTier')]",
"size": "[parameters('appServicePlanSkuSize')]",
"family": "[parameters('appServicePlanSkuFamily')]",
"capacity": "[parameters('appServicePlanSkuCapacity')]"
},
"kind": "linux",
"properties": {
"name": "[variables('appServicePlanName')]",
"reserved": true
}
}
The above template doesn't work now and will show the following logs-
[error]BadRequest: {
"Code": "BadRequest",
"Message": "The parameter LinuxFxVersion has an invalid value.",
"Target": null,
"Details": [
{
"Message": "The parameter LinuxFxVersion has an invalid value."
},
{
"Code": "BadRequest"
},
{
"ErrorEntity": {
"ExtendedCode": "01007",
"MessageTemplate": "The parameter {0} has an invalid value.",
"Parameters": [
"LinuxFxVersion"
],
"Code": "BadRequest",
"Message": "The parameter LinuxFxVersion has an invalid value."
}
}
],
"Innererror": null
}
To fix this, We can make it 2 steps deployment process as mentioned in the following blog
Updated Solution
According to https://learn.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-multiple#property-iteration it should be possible to create multiple values on a resource using copy but I can't make it work. Here is my code...
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appServiceName": {
"type": "string",
"metadata": {
"description": "Name of app service to apply SSL to."
}
},
"certificateName": {
"type": "string",
"metadata": {
"description": "User friendly certificate resource name"
}
},
"appServicePlan": {
"type": "string",
"metadata": {
"description": "App Service Plan Name"
}
},
"keyVaultId": {
"type": "string",
"metadata": {
"description": "Existing Key Vault resource Id with an access policy to allow Microsoft.Web RP to read Key Vault secrets (Checkout README.md for more information)"
}
},
"hostname": {
"type": "array",
"metadata": {
"description": "Custom hostname for creating SSL binding. This hostname should already be assigned to the Web App"
}
}
},
"resources": [
{
"apiVersion": "2017-03-30",
"type": "Microsoft.Web/sites",
"name": "[parameters('appServiceName')]",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Web/certificates', parameters('certificateName'))]"
],
"properties": {
"copy": [
{
"name": "hostnames",
"count": "[length(parameters('hostname'))]",
"input": {
"name": "[copyIndex('hostnames')]",
"properties": {
"hostNameSslStates": [
{
"name": "[[copyIndex(hostname)]]",
"sslState": "SniEnabled",
"thumbprint": "[reference(resourceId('Microsoft.Web/certificates', parameters('certificateName'))).Thumbprint]",
"toUpdate": true
}
]
}
}
}
]
}
},
{
"type": "Microsoft.Web/certificates",
"name": "[parameters('certificateName')]",
"apiVersion": "2016-03-01",
"location": "[resourceGroup().location]",
"properties": {
"keyVaultId": "[parameters('keyVaultId')]",
"keyVaultSecretName": "[parameters('certificateName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms',parameters('appServicePlan'))]"
}
}
]
}
And it returns Error: Code=InvalidTemplate; Message=Deployment template l
anguage expression evaluation failed: 'Unable to parse language expression 'copyIndex(hostname)]': expecte
d token 'LeftParenthesis' and actual 'RightParenthesis'.'. Please see https://aka.ms/arm-template-expressi
ons for usage details.
Any ideas what am I doing wrong?
Thanks in advance!
the error gives is away:
change "[[copyIndex(hostname)]]", to "[copyIndex('hostname')]"
you even have it right in other places, why not here?
and you probably want to do this:
"[parameters('hostname')[copyIndex('hostname')]]"
I have created the ARM template for azure web app. I need to publish the ARM template to azure market place. I have used the azure publish portal https://publish.windowsazure.com/workspace/multi-resource-solutions for publishing the ARM template.
To on-board the ARM template to azure market place zip file must contain a mainTemplate.json and createUiDefinition.json. I found some samples for createUiDefination.json file in https://github.com/Azure/azure-quickstart-templates but all the createUiDefination.json is for VM. I am unable to find the samples or tutorials for createUiDefination.json for Azure web app.
I need to validate the azure web app site name is already exists or not. Also need to create or use the app service plan.
Is there is any tutorial or sample for creating createUiDefination.json for azure web app?
I need to validate the azure web app site name is already exists or
not.
This is not possible, you need to add to the site name a unique String, to ensure that the site name is globally unique.
For example you could use in your ARM template this function: uniqueString()
Similar question was answered by a Microsoft employee.
Also need to create or use the app service plan.
Add an App Service Plan to your Azure Resource Manager Template. For example like this:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"hostingPlanName": {
"type": "string",
"minLength": 1
},
"skuName": {
"type": "string",
"defaultValue": "F1",
"allowedValues": [
"F1",
"D1",
"B1",
"B2",
"B3",
"S1",
"S2",
"S3",
"P1",
"P2",
"P3",
"P4"
],
"metadata": {
"description": "Describes plan's pricing tier and capacity. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/"
}
},
"skuCapacity": {
"type": "int",
"defaultValue": 1,
"minValue": 1,
"metadata": {
"description": "Describes plan's instance count"
}
}
},
"variables": {
"webSiteName": "[concat('webSite', uniqueString(resourceGroup().id))]"
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "[parameters('hostingPlanName')]",
"type": "Microsoft.Web/serverfarms",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "HostingPlan"
},
"sku": {
"name": "[parameters('skuName')]",
"capacity": "[parameters('skuCapacity')]"
},
"properties": {
"name": "[parameters('hostingPlanName')]"
}
},
{
"apiVersion": "2015-08-01",
"name": "[variables('webSiteName')]",
"type": "Microsoft.Web/sites",
"location": "[resourceGroup().location]",
"tags": {
"[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource",
"displayName": "Website"
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
],
"properties": {
"name": "[variables('webSiteName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
}
}
]
}
Validating an app service's name is possible in createUiDefinition.json.
The crux of it is the Microsoft.Solutions.ArmApiControl, which can be used to call ARM apis as part of the validation of a text box. Here is an example:
{
"$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#",
"handler": "Microsoft.Azure.CreateUIDef",
"version": "0.1.2-preview",
"parameters": {
"basics": [
{}
],
"steps": [
{
"name": "domain",
"label": "Domain Names",
"elements": [
{
"name": "domainInfo",
"type": "Microsoft.Common.InfoBox",
"visible": true,
"options": {
"icon": "Info",
"text": "Pick the domain name that you want to use for your app."
}
},
{
"name": "appServiceAvailabilityApi",
"type": "Microsoft.Solutions.ArmApiControl",
"request": {
"method": "POST",
"path": "[concat(subscription().id, '/providers/Microsoft.Web/checknameavailability?api-version=2021-02-01')]",
"body": "[parse(concat('{\"name\":\"', concat('', steps('domain').domainName), '\", \"type\": \"Microsoft.Web/sites\"}'))]"
}
},
{
"name": "domainName",
"type": "Microsoft.Common.TextBox",
"label": "Domain Name Word",
"toolTip": "The name of your app service",
"placeholder": "yourcompanyname",
"constraints": {
"validations": [
{
"regex": "^[a-zA-Z0-9]{4,30}$",
"message": "Alphanumeric, between 4 and 30 characters."
},
{
"isValid": "[not(equals(steps('domain').appServiceAvailabilityApi.nameAvailable, false))]",
"message": "[concat('Error with the url: ', steps('domain').domainName, '. Reason: ', steps('domain').appServiceAvailabilityApi.reason)]"
},
{
"isValid": "[greater(length(steps('domain').domainName), 4)]",
"message": "The unique domain suffix should be longer than 4 characters."
},
{
"isValid": "[less(length(steps('domain').domainName), 30)]",
"message": "The unique domain suffix should be shorter than 30 characters."
}
]
}
},
{
"name": "section1",
"type": "Microsoft.Common.Section",
"label": "URLs to be created:",
"elements": [
{
"name": "domainExamplePortal",
"type": "Microsoft.Common.TextBlock",
"visible": true,
"options": {
"text": "[concat('https://', steps('domain').domainName, '.azurewebsites.net - The main app service URL')]"
}
}
],
"visible": true
}
]
}
],
"outputs": {
"desiredDomainName": "[steps('domain').domainName]"
}
}
}
You can paste that in to the createUiDefinition.json sandbox azure provides to test it out.
To look at how to do an Azure App Service instead of a VM, look in to documentation like this and use it as the maintemplate.json in the app.zip package.
I'm trying to add the functionality to add the VM to a recovery services vault to my existing Azure ARM JSON template that I use for deployment.
I've used the code from the following GitHub template and this works if the recovery services vault is in the same resource group as VM but not if its in a different one.
https://github.com/Azure/azure-quickstart-templates/tree/master/101-recovery-services-backup-vms
The code is below:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"existingVirtualMachinesResourceGroup": {
"type": "string",
"metadata": {
"description": "Resource group where the virtual machines are located. This can be different than resource group of the vault. "
}
},
"existingVirtualMachines": {
"type": "array",
"metadata": {
"description": "Array of Azure virtual machines. e.g. [\"vm1\",\"vm2\",\"vm3\"]"
}
},
"existingRecoveryServicesVault": {
"type": "string",
"metadata": {
"description": "Recovery services vault name where the VMs will be backed up to. "
}
},
"existingBackupPolicy": {
"type": "string",
"defaultValue": "DefaultPolicy",
"metadata": {
"description": "Backup policy to be used to backup VMs. Backup POlicy defines the schedule of the backup and how long to retain backup copies. By default every vault comes with a 'DefaultPolicy' which canbe used here."
}
}
},
"variables": {
"backupFabric": "Azure",
"v2VmType": "Microsoft.Compute/virtualMachines",
"v2VmContainer": "iaasvmcontainer;iaasvmcontainerv2;",
"v2Vm": "vm;iaasvmcontainerv2;"
},
"resources": [
{
"name": "[concat(parameters('existingRecoveryServicesVault'), '/', variables('backupFabric'), '/', variables('v2VmContainer'), concat(parameters('existingVirtualMachinesResourceGroup'),';',parameters('existingVirtualMachines')[copyIndex()]), '/', variables('v2Vm'), concat(parameters('existingVirtualMachinesResourceGroup'),';',parameters('existingVirtualMachines')[copyIndex()]))]",
"apiVersion": "2016-06-01",
"location": "[resourceGroup().location]",
"type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems",
"copy": {
"name": "v2VmsCopy",
"count": "[length(parameters('existingVirtualMachines'))]"
},
"properties": {
"protectedItemType": "[variables('v2VmType')]",
"policyId": "[resourceId('Microsoft.RecoveryServices/vaults/backupPolicies',parameters('existingRecoveryServicesVault'),parameters('existingBackupPolicy') )]",
"sourceResourceId": "[resourceId(subscription().subscriptionId,parameters('existingVirtualMachinesResourceGroup'),'Microsoft.Compute/virtualMachines',parameters('existingVirtualMachines')[copyIndex()])]"
}
}
]
}
There is no variable or parameter to define the recovery services vaults resource group.
I've also looked at the following GitHub template that also adds a VM to a recovery services vault but again this doesn't seem to have the ability to use different resource groups.
https://github.com/Azure/azure-quickstart-templates/tree/master/201-recovery-services-backup-classic-resource-manager-vms
I've tried googling but so far I haven't been able to find an answer to this so is it possible?
Thanks
I've found the answer to this if it's useful to anyone else. As already mentioned the recovery services vault will use the same resource group as defined for the template. To be able to define a different template for the RSV this needs to be done using a nested template.
I have used the following nested template to replace the recovery services resource in my original post, the resource group required for the recovery services vault is defined by "resourceGroup": "[parameters('nestedTemplateRecoveryServicesResourceGroup')]",
{
"apiVersion": "2017-05-10",
"name": "nestedTemplateRecoveryServices",
"type": "Microsoft.Resources/deployments",
"resourceGroup": "[parameters('nestedTemplateRecoveryServicesResourceGroup')]",
"dependsOn": ["[concat('Microsoft.Compute/virtualMachines/', parameters('vmName'))]"],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"name": "[concat(parameters('existingRecoveryServicesVault'), '/', variables('backupFabric'), '/', variables('v2VmContainer'), concat(parameters('existingVirtualMachinesResourceGroup'),';',parameters('existingVirtualMachines')), '/', variables('v2Vm'), concat(parameters('existingVirtualMachinesResourceGroup'),';',parameters('existingVirtualMachines')))]",
"apiVersion": "2016-06-01",
"location": "[resourceGroup().location]",
"type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems",
"properties": {
"protectedItemType": "[variables('v2VmType')]",
"policyId": "[resourceId('Microsoft.RecoveryServices/vaults/backupPolicies',parameters('existingRecoveryServicesVault'),parameters('existingBackupPolicy') )]",
"sourceResourceId": "[resourceId(subscription().subscriptionId,parameters('existingVirtualMachinesResourceGroup'),'Microsoft.Compute/virtualMachines',parameters('existingVirtualMachines'))]"
}
}
]
},
"parameters": {},
"outputs": {}
}
}
Here's the JSON I use to achieve this - it scales when using copyIndex()...as included in the resource below. I've adapted this from Microsoft's own JSON template on the Azure Portal since they improved the VM deployment to include the option to backup during deployment.
I typically provide an array of VM names and I name other resources from those names using copy loops. Technically they're related to or children of the VM when being built so it seems most applicable to use the VM name as the basis of the name of an associated resource. I use copyIndex() to push in the name of the VM being iterated in that loop (index) which means all the sub-resources and nested templates can also use those parameters.
Anyway, this is the resource (a nested template, as you have to). Associated variables and parameters below.
{
"apiVersion": "2017-05-10",
"name": "[concat(parameters('virtualMachineNames')[copyIndex()], '-' , 'BackupIntent')]",
"type": "Microsoft.Resources/deployments",
"resourceGroup": "[parameters('DestinationRSVResourceGroup')]",
"copy": {
"name": "AzureBackupLoop",
"count": "[length(parameters('virtualMachineNames'))]"
},
"dependsOn": [
"NameOfPreviousLoop"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"name": "[concat(parameters('DestinationRecoveryServicesVault'), '/', 'Azure', '/', variables('v2Vm'), resourceGroup().name, ';', parameters('virtualMachineNames')[copyIndex()])]",
"apiVersion": "2017-07-01",
"type": "Microsoft.RecoveryServices/vaults/backupFabrics/backupProtectionIntent",
"properties": {
"friendlyName": "[concat(parameters('virtualMachineNames')[copyIndex()], 'BackupIntent')]",
"protectionIntentItemType": "AzureResourceItem",
"policyId": "[resourceId(parameters('DestinationRSVResourceGroup'), 'Microsoft.RecoveryServices/vaults/backupPolicies', parameters('DestinationRecoveryServicesVault'), parameters('existingBackupPolicy'))]",
"sourceResourceId": "[resourceId(resourceGroup().name, 'Microsoft.Compute/virtualMachines', parameters('virtualMachineNames')[copyIndex()])]"
}
}
]
}
}
}
The variables section from this template looks as follows:
"variables": {
"v2Vm": "vm;iaasvmcontainerv2;",
},
And finally the parameters (relevant to this resource):
"parameters": {
"DestinationRecoveryServicesVault": {
"type": "string",
"metadata": {
"description": "Name of the recovery services vault that the VM is to be backed up in to."
}
},
"existingBackupPolicy": {
"type": "string",
"metadata": {
"description": "Name of the backup policy that the VM will use."
}
},
"DestinationRSVResourceGroup": {
"type": "string",
"metadata": {
"description": "Resource group of the RSV."
}
}
"virtualMachineNames": {
"type": "array",
"metadata": {
"description": "An array of names for the VMs."
}
},
},
If you are going to use that Nested Template make sure you add the VM that you provisioned in the first template as a DependsOn. This way when it runs the incremental deployment the VM is already there.