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've got two Azure Function apps that use deployment slots, stage and production. These two Azure Function apps have about 50~ key:value pairs in Application Settings to define various API keys, application behavior, connection strings, etc.
I want to deploy these two Azure Function apps to five different environments (CI, DEV, QA, STG, PROD). I believe that deploying these resources to Azure using ARM templates is the better choice over Azure CLI. I will create tasks in my Azure DevOps release pipeline to achieve this.
In order to break down the ARM template into something easily maintainable, I wanted to create an ARM template parameter file for each environment. When defining the deployment file for the Azure Function, one of the properties to define is the siteConfig object, where you define the appSettings object with a NameValuePair object. For each environment, the stage and production slot will have different API keys, connection strings, and application behavior. My deployment file creates the Azure Function app with both the production slot and stage slot. In the deployment file, I have to provide the appSettings NameValuePair object twice. Then, I have to create 5 different parameter files for each environment. Multiply that by 2 because I have two slots.
Is it also true that all parameters defined in the parameter file have to be defined in the deployment template file in the parameters object?
Can I just pass in an array of objects with NameValuePairs from the parameter file so I don't have to have the entire list of parameters defined in the deployment file at the top and also under siteConfig.appSettings for the function app?
The documentation here shows that you can only provide an array of strings or a single object with many key:values. But appSettings is an array of objects where each object has 3 key:value pairs.
This is what the resource looks like in the deployment file. I would like to simply reference an entire array of objects from the parameter file, but it looks like the documentation states that I define all 50~ parameters at the top of the deployment file, which then the parameter file overrides when executed by Azure CLI or Azure DevOps task.
{
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"name": "[parameters('function-app-name')]",
"location": "[parameters('location')]",
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"kind": "functionapp",
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"siteConfig": {
"appSettings": [] # I need to provide an array of objects here
}
}
}
In addition to my complaint...I can't believe I'm going to have to create 20 parameter files for all five environments and their two Azure Functions that have two slots. Is there a better way to deploy to all my environments and their deployment slots using ARM templates and parameter files with their unique application settings?
UPDATE:
I was able to piece together various methods for creating environment-specific ARM templates and came up with the following result, with some inconvenient problems. First, I'll explain where I am now and then bring up the problems associated with the design.
In my deployment template, I've defined two parameters. Here they are:
"deploymentEnvironment": {
"type": "string",
"allowedValues": [
"CI",
"DEV",
"QA",
"TRN",
"STG",
"PROD"
],
"metadata": {
"description": "Type of environment being deployed to. AKA \"Stage\" in release definition."
}
},
"applicationSettings": {
"type": "object",
"metadata": {
"description": "Application settings from function.parameters.json"
}
}
My function.parameters.json has a structure like this:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"applicationSettings": {
"value": {
"CI": {
"appsetting1": "",
"appsetting2": ""
},
"DEV": {
"appsetting1": "",
"appsetting2": "" },
"QA": {
"appsetting1": "",
"appsetting2": ""
}
}
}
}
}
For each environment, I had placed all of my connection strings, apikeys, and application settings.
For the production slot for the function app, you can add a "resources" property which applies configuration to it. Here is the entire function app deployment:
{
"name": "[parameters('function-app-name')]",
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"kind": "functionapp",
"location": "[parameters('location')]",
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Insights/components/', variables('applicationInsightsName'))]",
"[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
"[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
],
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2018-11-01",
"properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
]
}
]
}
Next up was defining the stage slot deployment resource. Here it is:
{
"type": "Microsoft.Web/sites/slots",
"apiVersion": "2018-11-01",
"name": "[concat(parameters('function-app-name'), '/stage')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
],
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"kind": "functionapp",
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]"
},
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2018-11-01",
"properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]",
"[resourceId('Microsoft.Web/sites/slots/', parameters('function-app-name'), 'stage')]"
]
}
]
}
With this solution, I don't have to have a bunch of parameters.json files for each environment.
The problems...
Defining all of the application settings in parameters.json means I can't use template functions to get connection strings or Azure Key Vault values.
This is when I started to move some of the application settings to the deployment template to use template functions. So instead of having the APPINSIGHTS_INSTRUMENTATIONKEY and other AzureWebJobs* application settings in the parameters.json file, I provided the siteConfig object in the "properties" object for the Microsoft.Web/Sites resource and the Microsoft.Web/Sites/Slots resource.
This is the real bummer - When the deployment ran, it applied the siteConfig.appsettings values with the function app, then when it applied the parameters.json file, it deleted the application settings and applied only the ones from the json, instead of merging them together. That was a HUGE disappointment. In my initial testing with the AzureCLI, I used this command az functionapp config appsettings set --name $functionAppName --resource-group $resourceGroupName --settings $settingsFile --slot $slot to test what would happen with application settings that were not in a json file and was happy that it never deleted application settings. The powershell command gets and sets the values, merging it nicely and never deleting. But the ARM API deletes all those name value pairs and applies only what is defined. This means I cannot use template functions to create dynamic application settings and a json file to apply static application settings.
As of now, I feel like the only way to do a decent ARM template deployment is just deploy the resources without the siteConfig object or the config resource to apply application settings and then follow up with the Azure CLI to deploy the application settings. I suppose I could learn how to retrieve Key Vault secrets using Azure CLI or Azure DevOps pipeline tasks, but it would be even better to just have it all in a single ARM template.
For reference, here is my entire deployment template when I attempted to use dynamically generated appSettings and the config resource to define more appsettings.
{
"$schema": "https://schema.management.azure.com/schemas/2019-08-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"function-app-name": {
"defaultValue": "functionappname",
"type": "String",
"metadata": {
"description": "The name of the function app that you wish to create."
}
},
"sku": {
"type": "string",
"allowedValues": [
"S1",
"S2",
"S3"
],
"defaultValue": "S3",
"metadata": {
"description": "The pricing tier for the hosting plan."
}
},
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"metadata": {
"description": "Storage Account type"
}
},
"location": {
"type": "string",
"defaultValue": "southcentralus",
"metadata": {
"description": "Location for all resources."
}
},
"deploymentEnvironment": {
"type": "string",
"allowedValues": [
"CI",
"DEV",
"QA",
"TRN",
"STG",
"PROD"
],
"metadata": {
"description": "Type of environment being deployed to."
}
},
"applicationSettings": {
"type": "object",
"metadata": {
"description": "Application settings from function.parameters.json"
}
}
},
"variables": {
"storageAccountName": "[concat('store', uniquestring(resourceGroup().id))]",
"appServicePlanName": "[concat('ASP-', uniquestring(resourceGroup().id))]",
"applicationInsightsName": "[concat('appInsights-', uniquestring(resourceGroup().id))]",
"projectName": "DV"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-04-01",
"name": "[variables('storageAccountName')]",
"kind": "Storage",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('storageAccountType')]"
},
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
}
},
{
"name": "[variables('appServicePlanName')]",
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2019-08-01",
"location": "[parameters('location')]",
"properties": {
},
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"sku": {
"Name": "[parameters('sku')]",
"capacity": 2
},
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
]
},
{
"name": "[variables('applicationInsightsName')]",
"apiVersion": "2015-05-01",
"type": "Microsoft.Insights/components",
"kind": "web",
"location": "[parameters('location')]",
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"properties": {
"Application_Type": "web"
}
},
{
"name": "[parameters('function-app-name')]",
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"kind": "functionapp",
"location": "[parameters('location')]",
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
"siteConfig": {
"appSettings": [
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference(concat('microsoft.insights/components/', variables('applicationInsightsName'))).InstrumentationKey]"
},
{
"name": "AzureWebJobsDashboard",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~1"
}
]
}
},
"dependsOn": [
"[resourceId('Microsoft.Insights/components/', variables('applicationInsightsName'))]",
"[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
"[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
],
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2018-11-01",
"properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
]
}
]
},
{
"type": "Microsoft.Web/sites/slots",
"apiVersion": "2018-11-01",
"name": "[concat(parameters('function-app-name'), '/stage')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
],
"tags": {
"project": "[variables('projectName')]",
"env": "[parameters('deploymentEnvironment')]"
},
"kind": "functionapp",
"properties": {
"enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
"siteConfig": {
"appSettings": [
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference(concat('microsoft.insights/components/', variables('applicationInsightsName'))).InstrumentationKey]"
},
{
"name": "AzureWebJobsDashboard",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~1"
}
]
},
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2018-11-01",
"properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
]
}
]
}
}
]
}
Update 2:
I raised a github issue to have them fix the problem with ARM templates replacing all of the application settings on each deployment.
FWIW - I also voted on some Azure feedback post.
Sorry I don't have a huge amount of time to answer, and you have a bunch of questions which relate mainly to "what's the best way to...", and the answer is always "it depends".
One thing I find easier to manage is instead of using siteConfig to set all the app settings, you can create a top level resource of type Microsoft.Web/sites/config (which I find useful sometimes as you can create them after the site is created, so if you have dependencies elsewhere that aren't setup yet, it can be handy to separate out the config and the site).
"parameters": {
"appSettings": {
"type": "object",
"defaultValue": {
"property1": "value1",
"property2": "value2"
}
}
}
"resources": [
{
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"name": "[parameters('function-app-name')]",
"location": "[parameters('location')]",
"kind": "functionapp",
"properties": {
"enabled": true,
"serverFarmId": "..."
}
},
{
"type": "Microsoft.Web/sites/config",
"name": "[concat(parameters('function-app-name'), '/appsettings')]",
"apiVersion": "2018-11-01",
"properties": "[parameters('appSettings')]"
"dependsOn": [ "[resourceId('Microsoft.Web/sites/sites', parameters('function-app-name'))]",
}
]
One of the drawbacks of the above, is that you can't use certain functions in the params section, so you can't use listKeys() to get a key to a resource, so it's only useful sometimes, or like this example, if you wanted to add a reference to app insights which is also created in the same template, this isn't possible if you're passing in the settings as a param.
{
"type": "Microsoft.Web/sites/config",
"name": "[concat(parameters('function-app-name'), '/appsettings')]",
"apiVersion": "2018-11-01",
"properties": {
"property1": "value1",
"property2": "value2",
"APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('microsoft.insights/components/', variables('appInsightsName')), '2015-05-01').InstrumentationKey]"
}
"dependsOn": [
"[resourceId('Microsoft.Web/sites/sites', parameters('function-app-name'))]",
"[resourceId('microsoft.insights/components', variables('appInsightsName'))]"
}
You should really be resolving everything you can at deploy time, so a storage account (for example) connection string can be added into the template securely, and resolved at deploy time only.
Another handy tip, is to use key vault to store any secure credentials, api keys, connection strings etc that cannot be resolved in the template. You mention needing them, but then you're committing them to source control in the templates... Well, they wont stay secret very long (another tip, ensure they all use securestring instead of string types, otherwise the portal will expose them in the deployment logs for the resource group). You can access key vaults from app settings like this:
"secretConnectionString": "[concat('#Microsoft.KeyVault(SecretUri=https://', variables('vaultName'), '.vault.azure.net/secrets/my-connection-string/)')]",
But for the above to work, you will need to give your application read access to the vault "vaultName", which should be fine if you use managed service identities.
It is possible to combine static configuration with deployment-time references.
You use the union template function to combine your static configuration (object or array) with some deployment-time value that you wrap using the json template function.
In the example below I combine:
a static base config object
a static service specific config object
a deployment-time Application Insights config value
[union(
variables('appServiceBaseConfig'),
variables('appService1'),
json(
concat(
'{\"APPINSIGHTS_INSTRUMENTATIONKEY\":\"',
reference(concat('microsoft.insights/components/', variables('applicationInsightsName')), '2015-05-01').InstrumentationKey,
'\"}')
)
)
]
For anyone who comes to this thread, I wanted to offer an alternative to the suggestion above of using the union. I first went with that option but started to find it hard to use the string concatenation forming the json so wanted to offer this alternative:
[union(
variables('appServiceBaseConfig'),
variables('appService1'),
createObject(
'APPINSIGHTS_INSTRUMENTATIONKEY', reference(concat('microsoft.insights/components/', variables('applicationInsightsName')), '2015-05-01').InstrumentationKey,
'MY_VALUE', 'xyz'
)
)
]
The reason for this additional suggested answer is that the createObject function makes it much easier to construct the object if you have multiple keys values pairings.
Note: Multi-line functions like this are ONLY currently supported in ARM if you are using the Powershell cli as per this note in the docs. Took me a while to figure this out trying to deploy in Azure DevOps :facepalm:
To answer this piece:
Is it also true that all parameters defined in the parameter file have
to be defined in the deployment template file in the parameters
object?
Yes everything in the parameters file needs to be defined in the deployment file. The opposite is not true. Everything defined in your deployment file does not need to be defined in your parameters file. The definition in the deployment file can have a default value:
"location": {
"type": "string",
"defaultValue": "Central US",
"metadata": {
"description": "Specifies the Azure location where the key vault should be created."
}
},
Alternatively a parameter can be passed in as an override parameter in a release task.
just adding input to this thread to add my solution for this issue. I'm not sure if the overwriting App Settings issue has been fixed in normal ARM Templates, but the release pipeline step allows you to set additional app settings that get deployed when the app gets deployed. So, I have the dynamic app settings/the app settings that all my azure functions need defined on creation set in the ARM Template, and then I have additional app settings set in the release pipeline (using a variable group with secret variables to hide their values). Like so:
I am trying to create web app slots through ARM template.
I was able to create those but it looks like the default behavior is to create the them as a copy of the current web app state. This result in my slot inheriting app settings, connection strings, virtual directories, ....
Here a reproduction sample which demonstrate the behavior https://github.com/ggirard07/ARMSlotWebConfig.
I want my slot clean and fresh instead, which is the azure portal default behavior. The portal is able to allow a user to select the behavior by specifying the "configSource": "", value it posts when creating the slot.
Is there anyway to achieve the same from inside an ARM template?
To prevent the copying of settings from the production app, just add an empty siteConfig object in the slot properties. e.g.
{
"apiVersion": "2015-08-01",
"type": "slots",
"name": "maintenance",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Web/Sites/', variables('webSiteName'))]"
],
"properties": {
"siteConfig": { }
}
}
I sent a PR to illustrate on your repo.
Is there anyway to achieve the same from inside an ARM template?
If I use the template you mentioned, I also can reproduce it on my side. I also can't find a way to select the behavior by specifying the "configSource": "" directly, You could give feedback to Azure team.
I work it out with overriding the config during deploy slot. It works correctly on my side. You could use the following code to replace the creating WebApp slot code in your tempalte.
{
"apiVersion": "2015-08-01",
"name": "maintenance",
"type": "slots",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Web/Sites', variables('webSiteName'))]"
],
"properties": {
},
"resources": [
{
"apiVersion": "2015-08-01",
"type": "config",
"name": "connectionstrings",
"location": "East US",
"dependsOn": [
"[resourceId('Microsoft.Web/Sites/Slots', variables('webSiteName'), 'maintenance')]"
],
"properties": {}
},
{
"apiVersion": "2015-08-01",
"type": "config",
"name": "web",
"tags": {
"displayName": "Website configuration"
},
"dependsOn": [
"[resourceId('Microsoft.Web/Sites/Slots', variables('webSiteName'),'maintenance')]"
],
"properties": {
"virtualApplications": [
{
"virtualPath": "/",
"physicalPath": "site\\wwwroot",
"preloadEnabled": true,
"virtualDirectories": null
}
]
}
}
]
}
I'm using Azure ARM templates in order to make sure that I can repeatedly deploy uniform infrastructure and services. My ARM template consists of an App Service, Web App, Service Bus Queue, and Azure SQL database. On top of this, I'm setting up continuous deployment through VSTS.
Everything is working well EXCEPT I am not sure how to set a Service Bus SAS token at the Namespace level. I don't see a way in the Service Bus ARM template to specify one, so I cannot pre-generate a token and place it in my web.config file. I also don't see a way to have one generated on my behalf, then pull the values back to my web.config file. Any suggestions would be greatly appreciated.
I believe you have two options:
1) Get generated key from the output:
"outputs": {
"eh:Endpoint": {
"value": "[listKeys(resourceId('Microsoft.EventHub/namespaces/authorizationRules', variables('eventHubNamespaceName'), 'SendOnlyKey'),'2015-08-01').primaryKey]",
"type": "string"
},
}
And incorporate it in your build/release process.
2) Try to push a key with a template:
{
"apiVersion": "[parameters('eventHubVersion')]",
"name": "[variables('eventHubNamespaceName')]",
"type": "Microsoft.EventHub/namespaces",
"location": "[resourceGroup().location]",
"resources": [
{
"apiVersion": "2014-09-01",
"name": "[variables('eventHubName')]",
"type": "eventHubs",
"dependsOn": [
"[concat('Microsoft.EventHub/namespaces/', variables('eventHubNamespaceName'))]"
],
"properties": {
"path": "[variables('eventHubName')]",
"MessageRetentionInDays": "[parameters('messageRetentionInDays')]",
"PartitionCount": "[parameters('partitionCount')]"
},
"resources": [
{
"apiVersion": "[parameters('eventHubVersion')]",
"name": "StorageRetention",
"type": "consumergroups",
"dependsOn": [
"[variables('eventHubName')]",
"[concat('Microsoft.EventHub/namespaces/', variables('eventHubNamespaceName'))]"
],
"tags": {
"displayName": "eh"
}
}
]
},
{
"apiVersion": "[parameters('eventHubVersion')]",
"name": "[concat(variables('eventHubNamespaceName'),'/SendOnlyKey')]",
"type": "Microsoft.EventHub/namespaces/authorizationRules",
"dependsOn": [
"[concat('Microsoft.EventHub/namespaces/', variables('eventHubNamespaceName'))]"
],
"location": "[resourceGroup().location]",
"properties": {
"KeyName": "SendOnlyKey",
"ClaimType": "SendSharedAccessKey",
"ClaimValue": "None",
"PrimaryKey": "[parameters('eventHubSendPrimaryKey')]",
"SecondaryKey": "your_key",
"Rights": [ "Send" ],
"Revision": -1
}
},
{
"apiVersion": "[parameters('eventHubVersion')]",
"name": "[concat(variables('eventHubNamespaceName'),'/ListenOnlyKey')]",
"type": "Microsoft.EventHub/namespaces/authorizationRules",
"dependsOn": [
"[concat('Microsoft.EventHub/namespaces/', variables('eventHubNamespaceName'))]"
],
"location": "[resourceGroup().location]",
"properties": {
"KeyName": "ListenOnlyKey",
"ClaimType": "ReceiveSharedAccessKey",
"ClaimValue": "None",
"PrimaryKey": "your_key",
"SecondaryKey": "your_key",
"Rights": [ "Listen" ],
"Revision": -1
}
}
]
}
However note that the second solutions works only for an older version of API and sooner or later will be deprecated. Additionally I tested it only for pushing keys for a hub, not a namespace.
This might help others arriving at this answer
In EastUS using API_VERSION = 2017-04-01
The following will work to obtain references to primarykey and related fields
- connectionString: "[concat('',listKeys(resourceId('Microsoft.EventHub/namespaces/eventhubs/authorizationRules','{{ eh_namespace }}', '{{ eventhub_name }}','fw'), '2017-04-01').primaryConnectionString,'')]"
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