I'm provisioning several webapps in an ARM template, and I'm finding I'm having to have a lot of duplicated code to maintain a single config across multiple deployment slots. Both dependencies and properties have to be duplicated and maintained separately. I looked into using a variable, but a lot of my config is dependent on other resources and cannot be evaluated at the time variables are evaluated.
Ideally I'd like all slots to reference the same 'Microsoft.Web/sites/config' object, but I can't see a way to do that. My current deployment script looks like this (although this has been massively simplified, I have significantly more properties in reality)
{
"name": "[variables('siteName')]",
"type": "Microsoft.Web/sites",
"location": "[resourceGroup().location]",
"apiVersion": "2015-08-01",
"dependsOn": [
"[concat('Microsoft.Web/serverfarms/', variables('serverfarm'))]",
"[resourceid('Microsoft.EventHub/namespaces', variables('fullEventHubNameSpace'))]"
],
"tags": {
"[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('siteName'))]": "Resource"
},
"properties": {
"name": "[variables('siteName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('serverfarm'))]",
"siteConfig": {
"AlwaysOn": true
}
},
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2015-08-01",
"dependsOn": [
"[concat('Microsoft.Web/sites/', variables('siteName'))]",
"[concat('Microsoft.Insights/components/', variables('appInsightsSiteName'))]",
"[concat('Microsoft.Web/sites/', variables('otherSiteName'))]",
"[concat('Microsoft.DocumentDb/databaseAccounts/',variables('databaseAccountName'))]",
"[resourceid('Microsoft.EventHub/namespaces', variables('fullEventHubNameSpace'))]"
],
"properties": {
"APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.Insights/components', variables('appInsightsName'))).InstrumentationKey]",
"KEYVAULT_PATH": "[parameters('keyVaultPath')]",
"KEYVAULT_SECRET": "[parameters('keyVaultSecret')]",
"OTHER_SITE": "[reference(concat('Microsoft.Web/sites/', variables('otherSiteName'))).hostnames[0]]",
"DB_KEY": "[listKeys(resourceId(concat('Microsoft.DocumentDb/databaseAccounts'),variables('databaseAccountName')),'2015-04-08').primaryMasterKey]",
}
},
{
"apiVersion": "2015-08-01",
"name": "Staging",
"type": "slots",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Web/Sites', variables('siteName'))]"
],
"properties": {
},
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2015-08-01",
"dependsOn": [
"[concat('Microsoft.Web/sites/', variables('siteName'))]",
"[concat('Microsoft.Insights/components/', variables('appInsightsName'))]",
"[concat('Microsoft.DocumentDb/databaseAccounts/',variables('databaseAccountName'))]",
"[concat('Microsoft.Web/sites/', variables('otherSiteName'))]",
"[resourceid('Microsoft.EventHub/namespaces', variables('fullEventHubNameSpace'))]",
"Staging"
],
"properties": {
"APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.Insights/components', variables('appInsightsName'))).InstrumentationKey]",
"KEYVAULT_PATH": "[parameters('keyVaultPath')]",
"KEYVAULT_SECRET": "[parameters('keyVaultSecret')]",
"OTHER_SITE": "[reference(concat('Microsoft.Web/sites/', variables('otherSiteName'))).hostnames[0]]",
"DB_KEY": "[listKeys(resourceId(concat('Microsoft.DocumentDb/databaseAccounts'),variables('databaseAccountName')),'2015-04-08').primaryMasterKey]",
}
}
]
}
]
},
Is there any way to make this template more maintainable?
maybe you can take use of copy in your Template.
Move the section with your slot to root level in your template and add:
"copy": {
"name": "slotcopy",
"count": "[length(parameters('webSiteSlots'))]"
},
The name propperty shlould look like this:
"name": "[concat(parameters('webSiteName'), '/', parameters('webSiteSlots')[copyIndex()].name)]",
With this you say that this resource will be a child of the WebSite resource. More information about this here.
now you can add a parameter with a complex object array to your template:
"webSiteSlots": {
"type": "array",
"minLength": 0,
"defaultValue": []
}
in your parameters file you can now set a collection of slots you want to have with some additional values:
"webSiteSlots": {
"value": [
{
"name": "Staging",
"environment": "Production"
}
]
}
to use these given values you can do something like this:
"properties": {
"ASPNETCORE_ENVIRONMENT": "[parameters('webSiteSlots')[copyIndex()].environment]"
}
This should reduce the dublicated code a fair bit.
Greetings,
KirK
Related
I've set-up an App Service using the following ARM template snippet:
{
"name": "[variables('webBackEnd')]",
"type": "Microsoft.Web/sites",
"location": "[parameters('location')]",
"apiVersion": "2015-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
],
"tags": {
"[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName')))]": "Resource",
"displayName": "BackendWebApp"
},
"properties": {
"name": "[variables('webBackEnd')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
}
},
This will deploy an App Service. However, by default, it will be setup to use the .Net Framework. Below is a view from my Azure Portal:
In order to run my ASP.Net Core-based web server, I have to manually switch the Stack Settings from ".Net" to ".Net Core". It's a trivial thing to do, but I'd much rather configure it correctly through the ARM template. I searched Microsoft's docs but was unable to find the correct property. How does one go about to do this?
here's how an example web app looks when created from the portal:
{
"apiVersion": "2018-02-01",
"name": "[parameters('name')]",
"type": "Microsoft.Web/sites",
"location": "[parameters('location')]",
"properties": {
"name": "[parameters('name')]",
"siteConfig": {
"appSettings": [],
"metadata": [
{
"name": "CURRENT_STACK",
"value": "[parameters('currentStack')]"
}
]
},
// redacted some values
}
},
and the current stack value is dotnetcore
The accepted answer didn't work for me. I began my own investigation and finished with this code, which works in my case.
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"name": "[parameters('site_name')]",
"location": "[resourceGroup().location]",
.........................................
"resources": [
{
"name": "metadata",
"type": "config",
"apiVersion": "2018-11-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('site_name'))]"
],
"tags": {
},
"properties": {
"CURRENT_STACK": "dotnetcore"
}
}
]
According to documentation "An ARM template is idempotent, which means it can be executed as many times as you wish, and the result will be the same every time". But I just learned that when I redeploy AppService (without any changes) it removes my application. Endpoints were not responding anymore and there was no application logs so I went to Azure portal console, ran DIR, and to my surprise the only file that is there is hostingstart.html! Is it documented somewhere? This changes completely how I need to handle ARM templates in my Release pipeline.
I'm using linked templates. In main template I have this resource:
{
"name": "myApp",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"dependsOn": [
"storage"
],
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[uri(variables('templateBaseUrl'), 'myApp.json')]"
},
"parameters": {
"env": {
"value": "[parameters('env')]"
},
"myAppAppServiceSku": {
"value": "[parameters('myAppAppServiceSku')]"
},
"storageAccountName": {
"value": "[variables('storageAccountName')]"
}
}
}
}
and the linked template
"resources": [
{
"name": "[variables('myAppServerFarmName')]",
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2018-02-01",
"location": "[resourceGroup().location]",
"tags": {
"ENV": "[parameters('env')]"
},
"sku": {
"name": "[parameters('myAppAppServiceSku')]"
},
"properties": {
}
},
{
"name": "[variables('myAppWebSiteName')]",
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"dependsOn": [
"[variables('myAppServerFarmName')]"
],
"location": "[resourceGroup().location]",
"tags": {
"ENV": "[parameters('env')]"
},
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('myAppServerFarmName'))]",
"siteConfig": {
"alwaysOn": true
}
},
"resources": [
{
"name": "appSettings",
"type": "config",
"apiVersion": "2018-11-01",
"dependsOn": [
"[variables('myAppWebSiteName')]"
],
"tags": {
"ENV": "[parameters('env')]"
},
"properties": {
"storageAccountName": "[parameters('storageAccountName')]",
"storageKey": "[listKeys(resourceId('Microsoft.Storage/storageAccounts/', parameters('storageAccountName')), '2019-04-01').keys[0].value]"
}
}
]
}
]
EDIT:
I have checked with deployment using Kudu ZIP Deploy. And after this deployment it redeployment of ARM templates does not remove code! So it works as expected. So deployment from Release Pipelines is different in some way.
After I execute both steps everything looks fine. But when I then just execute first step application code is removed.
This is how it looks right now.
And steps have one task each:
See Azure Functions ARM template redeployment deletes my published functions for more info. Essentially you need to add this to your template:
{ "name": "WEBSITE_RUN_FROM_PACKAGE", "value": "1" }
I am working on ARM Templates, I have created the template file with two or more azure app services along with app service plan and then configured with VNET Integration of each app service.
This is sample JSON code:
{
"comments": "Web-App-01",
"name": "[variables('app_name_01')]",
"type": "Microsoft.Web/sites",
"location": "[variables('location')]",
"apiVersion": "2016-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('asp_name_01'))]"
],
"tags": {
"displayName": "[variables('app_name_01')]"
},
"properties": {
"name": "[variables('app_name_01')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('asp_name_01'))]",
"siteConfig": {
"alwaysOn": true
}
},
"resources": [
{
"type": "Microsoft.Web/sites/virtualNetworkConnections",
"name": "[concat(variables('app_name_01'), '/', variables('vnet_connection_name'),uniqueString('asdsdaxsdsd'))]",
"apiVersion": "2016-08-01",
"location": "[variables('location')]",
"properties": {
"vnetResourceId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vm_vnet_name'), variables('web_subnet_name'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('app_name_01'))]",
"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vm_vnet_name'), variables('web_subnet_name'))]"
]
}
]
},
{
"comments": "Web-App-02",
"name": "[variables('app_name_02')]",
"type": "Microsoft.Web/sites",
"location": "[variables('location')]",
"apiVersion": "2016-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('asp_name_02'))]"
],
"tags": {
"displayName": "[variables('app_name_02')]"
},
"properties": {
"name": "[variables('app_name_02')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('asp_name_01'))]",
"siteConfig": {
"alwaysOn": true
}
},
"resources": [
{
"type": "Microsoft.Web/sites/virtualNetworkConnections",
"name": "[concat(variables('app_name_02'), '/', variables('vnet_connection_name'),uniqueString('asdsdaxsdsd'))]",
"apiVersion": "2016-08-01",
"location": "[variables('location')]",
"properties": {
"vnetResourceId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vm_vnet_name'), variables('web_subnet_name'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('app_name_02'))]",
"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vm_vnet_name'), variables('web_subnet_name'))]"
]
}
]
}
The above code works fine for few azure app services, but for the rest of the app services I am getting internal server error or Conflict or Bad Request during VNET Integration of Azure App Service.
Note: When I deployed the above the JSON Code, the old VNET
integration is configured instead of New VNET (Preview) feature. So, I need to configure New VNET (Preview) feature for each app service.
So, can anyone suggest me how to resolve the above issue.
I've found a working example for this on an Azure Docs GitHub post:
How do we integrate the new vnet integrartion with ARM templates?
Seems to work a different way with the new VNet integration which uses a Microsoft.Web/sites/config sub-resource named virtualNetwork instead of the Microsoft.Web/sites/virtualNetworkConnections sub-resource
As well as a few requirements that need to be set on the target subnet / vnet (described in the link). The integration piece looks something like this:
{
"apiVersion": "2018-02-01",
"type": "Microsoft.Web/sites",
"name": "[parameters('appName')]",
"location": "[resourceGroup().location]",
...
"resources": [
{
"name": "virtualNetwork",
"type": "config",
"apiVersion": "2018-02-01",
"location": "[resourceGroup().location]",
"properties": {
"subnetResourceid": "[parameters('subnetResourceId')]",
"swiftSupported": true
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('appName'))]"
]
}
]
},
Apart from this I've not found much else documented, except for a reference to it in the azure-rest-api-specs which has the "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/networkConfig/virtualNetwork" endpoint defined:
azure-rest-api-specs / WebApps.json
It also seems (as the spec suggests) replacing "type": "config" with "type": "networkConfig" also works.
I've talked to a Prem Engineer of Microsoft.
The Key is to replace the Automation Template
{
"type": "Microsoft.Web/sites/virtualNetworkConnections",
"apiVersion": "2018-11-01",
"name": "[concat(parameters('sites_FelixOFA_name'), '/xxxxxxx_Functions')]",
"location": "West Europe",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('sites_FelixOFA_name'))]"
],
"properties": {
"vnetResourceId": "[concat(parameters('virtualNetworks_FelixODevPremNet_externalid'), '/subnets/Functions')]",
"isSwift": true
}
}
with
{
"type": "Microsoft.Web/sites/networkConfig",
"name": "[concat(parameters('webAppName'),'/VirtualNetwork')]",
"apiVersion": "2016-08-01",
"properties":
{
"subnetResourceId": "[parameters('subnetResourceId')]"
}
}
Where subnetResourceId is the resource id of their subnet – it should look like /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/virtualNetworks/{vnetName}/subnets/{subnetName}
I tried to reach same functionality by Bicep file, finally using
virtualNetworkSubnetId:
worked for me.
For example:
resource webAppName_resource 'Microsoft.Web/sites#2020-12-01' = {
name: '${webAppName}'
location: location
properties: {
serverFarmId: appServicePlanPortalName.id
virtualNetworkSubnetId: '${vnetDeploy_module.outputs.vnetid}/subnets/${vnetDeploy_module.outputs.subnetname}'
siteConfig: {
linuxFxVersion: linuxFxVersion
minTlsVersion: minTlsVersion
http20Enabled: http20Enabled
}
httpsOnly: httpsOnly
}
}
See templates in https://github.com/Azure/azure-quickstart-templates/tree/master/quickstarts/microsoft.web/app-service-regional-vnet-integration.
Result in ARM is:
{
"type": "Microsoft.Web/sites",
"apiVersion": "2021-01-01",
"name": "[parameters('appName')]",
"location": "[parameters('location')]",
"kind": "app",
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"virtualNetworkSubnetId": "[reference(resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))).subnets[0].id]",
"httpsOnly": true,
"siteConfig": {
"vnetRouteAllEnabled": true,
"http20Enabled": true
}
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
]
}
To fix this, I simply changed the isSwift or swiftSupported option to false.
I'm trying to turn WebSockets On for an Azure WebApp from an Azure ARM json template that deploys my whole infrastructure.
Here is an extract with regards to the Azure Web App. It doesn't work, i.e the WebSockets are still Off. I unsuccessfully tried different spelling: webSocketsEnabled or WebSockets.
"resources":[
{
"name": "[variables('MyApp')]",
"type": "Microsoft.Web/sites",
"location": "Brazil South",
"apiVersion": "2016-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('MyAppPlanBrazil'))]"
],
"tags": {
"[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', variables('MyAppPlanBrazil')))]": "Resource",
"displayName": "MyAppAppBrazil"
},
"properties": {
"name": "[variables('MyAppPlanBrazil')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('MyAppPlanBrazil'))]",
"siteConfig": {
"AlwaysOn": true,
"webSocketsEnabled": true,
"connectionStrings": [
{
...
},
{
...
},
]
}
}
]
UPDATE
As suggested in answer below I updated the apiVersion to "2016-08-01" but this still doesn't work.
Also note that while my schema is the one described here, apiVersion is squiggled in VS and it says the authorized value is "2015-08-01" only.
UPDATE2
I tried the solutions below. They work for their authors but not for me. I guess the problem is elsewhere. My infrastructure is already deployed and I try to update it with webSocketsEnabled. Whereas in the solution below I imagine the authors directly create the web app with webSocketsEnabled.
Also, I coupled webSocketsEnabled with alwaysOn whereas the pricing tier of my webapp doesn't allow "AlwaysOn" (as it says in the portal I need to upgrade to use that feature) so I'll try without alwaysOn.
UPDATE3
At the end, the above template worked when I removed AlwaysOn.
Thank you to those who tried to help me.
Set your api version to this: "2016-08-01"
Use
"webSocketsEnabled": true
This is from the Microsoft.Web/sites template reference:
https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites
The api version you are using (2015-08-01) from:
https://github.com/Azure/azure-resource-manager-schemas/blob/master/schemas/2015-08-01/Microsoft.Web.json
Doesn't have web sockets in it, but the later one:
https://github.com/Azure/azure-resource-manager-schemas/blob/master/schemas/2016-08-01/Microsoft.Web.json
Does have webSocketsEnabled.
Please have a try to use the following code. It works correctly on my side.
Updated: add whole test arm template and you could have a try to use the following code with your service plan name and resource group name
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"serverFarmName": {
"type": "string",
"defaultValue": "YourPlan"
},
"serverFarmResourceGroup": {
"type": "string",
"defaultValue": "ResourceGroupName"
}},
"variables": {
"ARMtemplateTestName": "[concat('ARMtemplateTest', uniqueString(resourceGroup().id))]"},
"resources": [
{
"name": "[variables('ARMtemplateTestName')]",
"type": "Microsoft.Web/sites",
"location": "southcentralus",
"apiVersion": "2015-08-01",
"dependsOn": [ ],
"tags": {
"[concat('hidden-related:', resourceId(parameters('serverFarmResourceGroup'), 'Microsoft.Web/serverFarms', parameters('serverFarmName')))]": "Resource",
"displayName": "ARMtemplateTest"
},
"properties": {
"name": "[variables('ARMtemplateTestName')]",
"serverFarmId": "[resourceId(parameters('serverFarmResourceGroup'), 'Microsoft.Web/serverFarms', parameters('serverFarmName'))]"
},
"resources": [
{
"name": "web",
"type": "config",
"apiVersion": "2015-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('ARMtemplateTestName'))]"
],
"tags": {
"displayName": "enableWebSocket"
},
"properties": {
"webSocketsEnabled": true,
"alwaysOn": true
}
},
{
"apiVersion": "2015-08-01",
"name": "connectionstrings",
"type": "config",
"dependsOn": [
"[resourceId('Microsoft.Web/Sites', variables('ARMtemplateTestName'))]"
],
"properties": {
"ConnString1": {
"value": "My custom connection string",
"type": "custom"
},
"ConnString2": {
"value": "My SQL connection string",
"type": "SQLAzure"
}
}
},
{
"name": "appsettings",
"type": "config",
"apiVersion": "2015-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('ARMtemplateTestName'))]"
],
"tags": {
"displayName": "Appsetting"
},
"properties": {
"key1": "value1",
"key2": "value2"
}
}
]
}],
"outputs": {}
}
Test Result:
All the above solution should work.
My initial snippet worked as well ... as soon as I removed alwaysOn.
Indeed, I was using a free tiers App Service Plan for which alwaysOn is not available. While there was no errors or anything else indicating something wrong, I could not set webSocketEnabled and alwaysOn at the same time in that case.
I'm struggling with a ARM deployment script for my node.js application. If I point to a repo with an MVC application it all works fine, but not using an node.js app.
Are there any specific settings for node.js sites?
Here is the resource part of my script:
{
"apiVersion": "2015-08-01",
"name": "[parameters('nodeName')]",
"type": "Microsoft.Web/sites",
"location": "[resourceGroup().location]",
"tags": {
"[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('nodeName'))]": "Resource",
"displayName": "Website"
},
"dependsOn": [
"[concat('Microsoft.Web/serverfarms/', parameters('nodeName'))]"
],
"resources": [
{
"apiVersion": "2015-08-01",
"name": "web",
"type": "sourcecontrols",
"dependsOn": [
"[resourceId('Microsoft.Web/Sites', concat(parameters('nodeName')))]"
],
"properties": {
"repoUrl": "https://github.com/microServiceBus/microservicebus.node.git",
"branch": "master",
"IsManualIntegration": true
}
}
],
"properties": {
"name": "[parameters('nodeName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('nodeName'))]",
"siteConfig": {
"appSettings": [
{
"Name": "hubUri",
"Value": "[parameters('hubUri')]"
},
{
"Name": "nodeName",
"Value": "[parameters('nodeName')]"
},
{
"Name": "organizationaId",
"Value": "[parameters('organizationaId')]"
}
]
}
}
}
The issue was that the name of the repo had a dot in it (microservicebus.node.git). I believe the bug is fixed or is going to be fixed soon.