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.
Related
Hello I am trying to deploy this template via azure devops and get this error:
'The template resource '' of type 'microsoft.insights/workbooks' at line '1' and column '1512' is not valid. The name property cannot be null or empty. Please see https://aka.ms/arm-template/#resources for usage details.'." AdditionalInfo=[{"info":{"lineNumber":1,"linePosition":1512,"path":"properties.template.resources[0]"},"type":"TemplateViolation"}]
Template:
{
"contentVersion": "1.0.0.0",
"parameters": {
"workbookDisplayName": {
"type": "string",
"defaultValue": "Azure Firewall Workbook",
"metadata": {
"description": "The friendly name for the workbook that is used in the Gallery or Saved List. This name must be unique within a resource group."
}
},
"workbookType": {
"type": "string",
"allowedValues": [
"workbook",
"sentinel"
],
"defaultValue": "workbook",
"metadata": {
"description": "The gallery that the workbook will been shown under. Supported values include workbook, tsg, etc. Usually, this is 'workbook'"
}
},
"DiagnosticsWorkspaceName": {
"type": "string",
"defaultValue": "WorkspaceName",
"metadata": {
"description": "Provide the workspace name for your Network Diagnostic logs"
}
},
"DiagnosticsWorkspaceSubscription": {
"type": "string",
"defaultValue": "WorkspaceSubscriptionID",
"metadata": {
"description": "Provide the workspace subscription GUID for your Network Diagnostic logs"
}
},
"DiagnosticsWorkspaceResourceGroup": {
"type": "string",
"defaultValue": "ResourceGroupName",
"metadata": {
"description": "Provide the workspace resourcegroupname for your Network Diagnostic logs"
}
},
"workbookId": {
"type": "string",
"defaultValue": "[newGuid()]",
"metadata": {
"description": "The unique guid for this workbook instance"
}
}
},
"variables": {
"workbookSourceId": "[concat('/subscriptions/',parameters('DiagnosticsWorkspaceSubscription'),'/resourcegroups/', parameters('DiagnosticsWorkspaceResourceGroup'), '/providers/Microsoft.OperationalInsights/workspaces/',parameters('DiagnosticsWorkspaceName'))]"
},
"resources": [
{
"name": "[parameters('workbookId')]",
"type": "microsoft.insights/workbooks",
"location": "[resourceGroup().location]",
"apiVersion": "2018-06-17-preview",
"dependsOn": [],
"kind": "shared",
"properties": {
"displayName": "[parameters('workbookDisplayName')]"}",
"version": "1.0",
"sourceId": "[variables('workbookSourceId')]",
"category": "[parameters('workbookType')]"
}
}
],
"outputs": {
"workbookId": {
"type": "string",
"value": "[resourceId( 'microsoft.insights/workbooks', parameters('workbookId'))]"
}
},
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#"
}
Looks like there are some syntactical errors in the above shared ARM template .
"properties": {
"displayName": "[parameters('workbookDisplayName')]"}",
"version": "1.0",
"sourceId": "[variables('workbookSourceId')]",
"category": "[parameters('workbookType')]"
}
As per the Azure documentation for the Microsoft.Insights/Workbooks sourceID,Category & Version are the parameters of the properties block.
When we have loaded the above shared Json template/ARM template in our VisualStudioCode,the intellisense has found few syntactical errors & also thrown error stating serializedData parameter is mandatory in the properities block of Microsoft.Insights/Workbooks.
Here is the ARM template post fixing those errors & tried deploying in our local environment which got successfully deployed without any issues.
{
"contentVersion": "1.0.0.0",
"parameters": {
"workbookDisplayName": {
"type": "string",
"defaultValue": "Azure Firewall Workbook",
"metadata": {
"description": "The friendly name for the workbook that is used in the Gallery or Saved List. This name must be unique within a resource group."
}
},
"workbookType": {
"type": "string",
"allowedValues": [
"workbook",
"sentinel"
],
"defaultValue": "workbook",
"metadata": {
"description": "The gallery that the workbook will been shown under. Supported values include workbook, tsg, etc. Usually, this is 'workbook'"
}
},
"DiagnosticsWorkspaceName": {
"type": "string",
"defaultValue": "WorkspaceName",
"metadata": {
"description": "Provide the workspace name for your Network Diagnostic logs"
}
},
"DiagnosticsWorkspaceSubscription": {
"type": "string",
"defaultValue": "WorkspaceSubscriptionID",
"metadata": {
"description": "Provide the workspace subscription GUID for your Network Diagnostic logs"
}
},
"DiagnosticsWorkspaceResourceGroup": {
"type": "string",
"defaultValue": "ResourceGroupName",
"metadata": {
"description": "Provide the workspace resourcegroupname for your Network Diagnostic logs"
}
},
"workbookId": {
"type": "string",
"defaultValue": "[newGuid()]",
"metadata": {
"description": "The unique guid for this workbook instance"
}
}
},
"variables": {
"workbookSourceId": "[concat('/subscriptions/',parameters('DiagnosticsWorkspaceSubscription'),'/resourcegroups/', parameters('DiagnosticsWorkspaceResourceGroup'), '/providers/Microsoft.OperationalInsights/workspaces/',parameters('DiagnosticsWorkspaceName'))]"
},
"resources": [
{
"name": "[parameters('workbookId')]",
"type": "microsoft.insights/workbooks",
"location": "[resourceGroup().location]",
"apiVersion": "2018-06-17-preview",
"dependsOn": [],
"kind": "shared",
"properties" : {
"displayName": "[parameters('workbookDisplayName')]",
"sourceId": "[variables('workbookSourceId')]",
"category": "[parameters('workbookType')]",
"serializedData" : "",
"version" : "1.0"
}
}
],
"outputs": {
"workbookId": {
"type": "string",
"value": "[resourceId( 'microsoft.insights/workbooks', parameters('workbookId'))]"
}
},
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"
}
Note:
In the above shared template, we observed that the schema version in the deployment template you are using is 2015-01-01 its always suggested to use latest version 2019-04-01
Here is the output sample for our reference:
I've written up the script below to do the following:
Provision a resource group
In a separate deployment:
Provision a storage account
Provision a server farm
Provision a function app
The problem lies in the setting of the app settings in the function app, when I'm setting up the AzureWebJobsStorage. The resourceId function fails to resolve the storage account. When looking at the documention for the resourceId function, it states:
When used with a subscription-level deployment, the resourceId() function can only retrieve the ID of resources deployed at that level. [docs]
But now I don't know how to resolve this!
Template:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"resourceGroupName": {
"type": "string"
},
"functionName": {
"type": "string"
},
"storageAccName": {
"type": "string"
},
"namingPrefix": {
"type": "string"
}
},
"variables": {
"resourceGroupLocation": "North Europe",
"planName": "[replace(concat(variables('resourceGroupLocation'), 'Plan'),' ','')]",
"resourceGroupName": "[concat(parameters('namingPrefix'), '-', parameters('resourceGroupName'))]",
"functionName": "[concat(parameters('namingPrefix'), '-', parameters('functionName'))]",
"storageAccName": "[toLower(concat(parameters('namingPrefix'), parameters('storageAccName')))]"
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"location": "[variables('resourceGroupLocation')]",
"name": "[variables('resourceGroupName')]",
"properties": {}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-05-01",
"name": "NestedTemplate",
"resourceGroup": "[variables('resourceGroupName')]",
"dependsOn": [
"[variables('resourceGroupName')]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-04-01",
"name": "[variables('storageAccName')]",
"location": "[variables('resourceGroupLocation')]",
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"kind": "Storage",
"properties": {
"networkAcls": {
"bypass": "AzureServices",
"virtualNetworkRules": [],
"ipRules": [],
"defaultAction": "Allow"
},
"supportsHttpsTrafficOnly": true,
"encryption": {
"services": {
"file": {
"enabled": true
},
"blob": {
"enabled": true
}
},
"keySource": "Microsoft.Storage"
}
}
},
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2016-09-01",
"name": "[variables('planName')]",
"location": "[variables('resourceGroupLocation')]",
"sku": {
"name": "Y1",
"tier": "Dynamic",
"size": "Y1",
"family": "Y",
"capacity": 0
},
"kind": "functionapp",
"properties": {
"name": "[variables('planName')]",
"computeMode": "Dynamic",
"perSiteScaling": false,
"reserved": false,
"targetWorkerCount": 0,
"targetWorkerSizeId": 0
}
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2016-08-01",
"name": "[variables('functionName')]",
"location": "[variables('resourceGroupLocation')]",
"dependsOn": [
"[variables('planName')]",
"[variables('appInsightsName')]",
"[variables('storageAccName')]"
],
"kind": "functionapp",
"identity": {
"type": "SystemAssigned"
},
"properties": {
"enabled": true,
"hostNameSslStates": [
{
"name": "[concat(variables('functionName'), '.azurewebsites.net')]",
"sslState": "Disabled",
"hostType": "Standard"
},
{
"name": "[concat(variables('functionName'), '.scm.azurewebsites.net')]",
"sslState": "Disabled",
"hostType": "Repository"
}
],
"siteConfig": {
"appSettings": [
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).key1)]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).key1)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[variables('functionName')]"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "node"
},
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "10.14.1"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~2"
}
]
},
"serverFarmId": "[variables('planName')]",
"reserved": false
}
}
]
}
}
}
]
}
Executed using following line:
New-AzDeployment -Location "North Europe" -TemplateFile $TemplateFilePath -TemplateParameterFile $ParametersFilePath -namingPrefix $namingPrefix;
Output
Resource Microsoft.Storage/storageAccounts 'testStorageAccount' failed with message '{
"error": {
"code": "ResourceNotFound",
"message": "The Resource 'Microsoft.Storage/storageAccounts/testStorageAccount' under resource group '<null>'
was not found."
}
}'
You've run into a few "limitations" in the template language that make this hard at the moment (we're working on improving both).
1) Inline nested deployments have the scope of the top-level deployment when evaluating template language expressions (anything in []) which is sometimes convenient (you can share variables for example) but more often than not causes some problem (like the resourceId function). ARM has always behaved this way but with the advent of subscription level deployments it's a bit more problematic (you run into it more). To get around this you can use linked templates - I know that's not always ideal but they will behave as expected.
2) the second thing you're running into is that list*() functions are evaluated immediately if ARM thinks the resource you're accessing is not within the same deployment. Due to #1, that's what ARM thinks in this case and why trying to concat() the resourceID still doesn't work.
Aside from that, stay away from the providers() function for apiVersions, it's not deterministic and the results of that function can change without you knowing it. The code you had in your original post for listKeys did work a while back and you might see it in samples floating around, but changes in the platform can break that function's behavior. Literal apiVersions are always better in ARM templates.
The docs are confusing and don't describe how resourceId() works at that level. It should really say:
When used in a subscription level deployment resourceId() can only get the resource IDs of resource groups (Microsoft.Resources/resourceGroups), policies (Microsoft.Authorization/policyAssignments), and role definitions (Microsoft.Authorization/roleDefinitions), as these are subscription level specific resources.
Since that's how it actually works. More docs here.
In terms of how to proceed from here, you'll simply need to deploy the resource groups in one template at the subscription level, and the resources in another template at the resource group level.
I hit this issue and found that they more recently support nested templates better by defining inner and outer scopes on the deployment resources and function scopes.
https://learn.microsoft.com/bs-latn-ba/azure/azure-resource-manager/templates/cross-scope-deployment?tabs=azure-cli#how-functions-resolve-in-scopes
https://www.youtube.com/watch?v=96XxVyxrhZI
For anyone who runs into this issue at a later date (probably myself) I was forced to create the resource group in my powershell script, and then use new-AzResourceGroupDeployment instead.
To accommodate this, the changes to the deployment template were minimal (I remove the resource group and brought the nested template one level up). However, I was also accessing the storage account key incorrectly. This has been updated in the code below.
$resourceGroup = Get-AzResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue
if(!$resourceGroup)
{
Write-Host "Creating resource group '$resourceGroupName' in location '$resourceGroupLocation'";
New-AzResourceGroup -Name $resourceGroupName -Location $resourceGroupLocation
}
else{
Write-Host "Using existing resource group '$resourceGroupName'";
}
# Start the deployment
Write-Host "Starting deployment...";
if(Test-Path $parametersFilePath) {
New-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile $TemplateFilePath -TemplateParameterFile $parametersFilePath;
}
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"resourceGroupName": {
"type": "string"
},
"functionName": {
"type": "string"
},
"storageAccName": {
"type": "string"
},
"namingPrefix": {
"type": "string"
}
},
"variables": {
"resourceGroupLocation": "North Europe",
"planName": "[replace(concat(variables('resourceGroupLocation'), 'Plan'),' ','')]",
"resourceGroupName": "[concat(parameters('namingPrefix'), '-', parameters('resourceGroupName'))]",
"functionName": "[concat(parameters('namingPrefix'), '-', parameters('functionName'))]",
"storageAccName": "[toLower(concat(parameters('namingPrefix'), parameters('storageAccName')))]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-04-01",
"name": "[variables('storageAccName')]",
"location": "[variables('resourceGroupLocation')]",
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"kind": "Storage",
"properties": {
"networkAcls": {
"bypass": "AzureServices",
"virtualNetworkRules": [],
"ipRules": [],
"defaultAction": "Allow"
},
"supportsHttpsTrafficOnly": true,
"encryption": {
"services": {
"file": {
"enabled": true
},
"blob": {
"enabled": true
}
},
"keySource": "Microsoft.Storage"
}
}
},
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2016-09-01",
"name": "[variables('planName')]",
"location": "[variables('resourceGroupLocation')]",
"sku": {
"name": "Y1",
"tier": "Dynamic",
"size": "Y1",
"family": "Y",
"capacity": 0
},
"kind": "functionapp",
"properties": {
"name": "[variables('planName')]",
"computeMode": "Dynamic",
"perSiteScaling": false,
"reserved": false,
"targetWorkerCount": 0,
"targetWorkerSizeId": 0
}
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2016-08-01",
"name": "[variables('functionName')]",
"location": "[variables('resourceGroupLocation')]",
"dependsOn": [
"[variables('planName')]",
"[variables('appInsightsName')]",
"[variables('storageAccName')]"
],
"kind": "functionapp",
"identity": {
"type": "SystemAssigned"
},
"properties": {
"enabled": true,
"hostNameSslStates": [
{
"name": "[concat(variables('functionName'), '.azurewebsites.net')]",
"sslState": "Disabled",
"hostType": "Standard"
},
{
"name": "[concat(variables('functionName'), '.scm.azurewebsites.net')]",
"sslState": "Disabled",
"hostType": "Repository"
}
],
"siteConfig": {
"appSettings": [
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[variables('functionName')]"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "node"
},
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "10.14.1"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~2"
}
]
},
"serverFarmId": "[variables('planName')]",
"reserved": false
}
}
]
}
Edited -
Sorry I jumped the gun earlier, the issue is with New-AzDeployment which is specifically for deploying subscription level resources.
https://learn.microsoft.com/en-us/powershell/module/az.resources/new-azdeployment?view=azps-2.7.0
Excerpts from the link above -
The New-AzDeployment cmdlet adds a deployment at the current
subscription scope. This includes the resources that the deployment
requires.
An Azure resource is a user-managed Azure entity. A resource can live
in a resource group, like database server, database, website, virtual
machine, or Storage account. Or, it can be a subscription level
resource, like role definition, policy definition, etc.
To add resources to a resource group, use the
New-AzResourceGroupDeployment which creates a deployment at a resource
group. The New-AzDeployment cmdlet creates a deployment at the current
subscription scope, which deploys subscription level resources.
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 am new to google fit api. I was exploring my own fit account. I find this below data source id in my datasources.
Below is the exact dataSource
{
"dataStreamId": "derived:com.google.activity.segment:com.google.android.gms:OnePlus:ONE A2003:10398c01:from_sample<-derived:com.google.activity.sample:com.google.android.gms:OnePlus:ONE A2003:10398c01:detailed",
"dataStreamName": "from_sample<-derived:com.google.activity.sample:com.google.android.gms:OnePlus:ONE A2003:10398c01:detailed",
"type": "derived",
"dataType": {
"name": "com.google.activity.segment",
"field": [
{
"name": "activity",
"format": "integer"
}
]
},
"device": {
"uid": "10398c01",
"type": "phone",
"version": "",
"model": "ONE A2003",
"manufacturer": "OnePlus"
},
"application": {
"packageName": "com.google.android.gms",
"version": "1"
}
}
What is meaning of a dataStreamId
dataStreamId is a property that includes a unique identifier- a set of numbers. This is your developer project number, and it will be the same for all requests made using that particular developer account. More of that here.