Merge App Settings With Bicep
Introduction
ARM templates follow the Desired State Configuration (DSC)
pattern. This means that the ARM template describes what the desired Azure infrastructure should look like. When an ARM template is applied to an Azure environment, the differences between the template and the current state are calculated and applied. The end result is an environment that conforms to the ARM template.
While this is true for many of the Azure resources, it may not always be true for properties of ARM resources. A few examples are App Service App Settings (Configuration
as it’s called in the Azure Portal) and Key Vault Access Policies. In these particular cases the last deployment replaces the complete set of app settings or access policies. This isn’t exactly what you would expect and certainly not what you’d want.
Bicep
We’ve recently started using Bicep instead of ARM templates. With Bicep, Microsoft starts treating ARM templates as Intermediate Language (IL). In other words, you’re encouraged no longer to write ARM templates directly, but instead use one of the higher level languages like Bicep. If you haven’t heard of the Bicep language for ARM templates, you should definitely check it out. It makes authoring Azure deployments so much easier.
The WEBSITE_RUN_FROM_PACKAGE paradox
I ran into an issue recently where I needed to create an Azure Function and publish the code for it. A good practice is to run your Azure Functions from a package file. When you do so your files are deployed as a zip package instead of separate files. For Azure Functions to understand that you’re running from a package file, a configuration setting WEBSITE_RUN_FROM_PACKAGE
with a value of 1
is added to the configuration of the Azure Function once you publish your function.
This introduces the following paradox:
- An empty Azure Function (one without code published to it) can only be deployed to when the
WEBSITE_RUN_FROM_PACKAGE
settings doesn’t exist or its value is0
- An Azure Function running a package can only be deployed to when the
WEBSITE_RUN_FROM_PACKAGE
setting has a value of1
See the problem? We could do two things:
- Include the
WEBSITE_RUN_FROM_PACKAGE
setting in the ARM template. This would make the Azure Function host unreachable when we initially create the resource, so we can’t deploy anything to it. - Not include the
WEBSITE_RUN_FROM_PACKAGE
setting in the ARM template. Everything would be fine when the resources is created initially, but with every update the ARM template would remove theWEBSITE_RUN_FROM_PACKAGE
setting from the configuration and the Azure Function would become unreachable for deployment.
Merging App Settings
The solution would be to merge the app settings, so that when a package file is deployed and the WEBSITE_RUN_FROM_PACKAGE
setting exists, it is preserved. It can be done with ARM templates, but it’s a lot easier with Bicep.
To merge app settings (and this concept should translate to access policies as well) we use two Bicep files:
main.bicep
, which is our regular Bicep templateappSettings.bicep
, which is a Bicep module that we call from main.bicep and takes care of applying the app settings
AppSettings.bicep
The appSettings.bicep module is fairly simple. It takes two parameters:
- currentAppSettings, containing the current app settings
- appSettings, the new app settings to apply
It uses the union function to combine the current with the new app settings and apply them to the app service. The combine function automatically removes duplicate values where the values of the last parameter win.
param currentAppSettings object
param appSettings object
resource siteconfig 'Microsoft.Web/sites/config@2020-12-01' = {
name: myAwesomeFunction/appSettings
properties: union(currentAppSettings, appSettings)
}
Main.bicep
In the main.bicep file you create the app service as usual. Then we call the appSettings.bicep module and use the list
function to retrieve the current app settings and forward it to the module.
Your main.bicep should look a little like this:
resource appServicePlan 'Microsoft.Web/serverfarms@2020-12-01' existing = {
name: 'myAppServicePlan'
}
resource myAwesomeFunction 'Microsoft.Web/sites@2020-12-01' = {
name: 'myAwesomeFunction'
location: resourceGroup().location
kind: 'functionapp'
properties: {
serverFarmId: appServicePlan.id
}
}
module appSettings 'appSettings.bicep' = {
name: 'myAwesomeFunction-appSettings'
params: {
currentAppSettings: list('{myAwesomeFunction.id}/config/appsettings', '2020-12-01').properties
appSettings: {
'Foo': 'Bar'
}
}
}