How to deploy App Service settings via Azure DevOps pipeline

How to deploy App Service settings via Azure DevOps pipeline

Introduction

In this article, I am going to demonstrate a building block that allows pushing run-time parameters to an App Service configuration via an Azure DevOps pipeline.

It is not far to seek that one would tend to use the Azure App Service Settings task (AzureAppServiceSettings@1) for such an exercise. However, what's special about this proposal is that we are going to leverage Azure's App Configuration Service instead together with an Azure CLI task. The following diagram depicts the intended setup.

One can argue that I am misusing the App Configuration Service, as its main purpose is slightly different than what we are going to use it for...

What is the Application Config Store? - A fast, scalable parameter storage for app configuration, that is complementary to Azure Key Vault. It helps you manage application settings and control their access centrally.  It also simplifies your deployment tasks and eases the burden of dealing with permutations of configurations created by multiple applications, dependencies, and environments.

... usually, you'd use a client library in conjunction with this service to retrieve parameters for your application at run-time (such as the Microsoft.Azure.AppConfiguration.AspNetCore).

At the same time, it provides benefits such as feature toggling, reacting to events, point-in-time snapshots, and other benefits, to name a few. So it's more of a developers tool than a DevOps tool 😊

Anyway, we are going to make use of the import/export function, as it allows us to directly export key-value pairs to an App Service Configuration.

But hey, what's wrong with the Azure App Service Settings Task?

First of this task tigthly couples configurational aspects and the deployment logic to each other. To mitigate that, one might want to create a variable group per environment and then pass them to the task.

However, I find that needlessly effortful, as you would have to create the keys twice, which can be error-prone. At the same time, configurational parameters would live, and have to be maintained, in Azure DevOps. I rather prefer them to live in the Azure service.

- task: AzureAppServiceSettings@1
  displayName: Azure App Service Settings
  inputs:
    azureSubscription: $(azureSubscription)
    appName: $(WebApp_Name)
    appSettings: |
      [
        {
          "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
          "value": "<some instrumentation key>",
          "slotSetting": false
        },
        {
          "name": "MYSQL_DATABASE_NAME",
          "value": "<some database name>", 
          "slotSetting": false
        },
        
        // ...
      ]
The Azure App Service Settings task in action

Next, when dealing with many configuration parameters your pipeline can become confusingly long. To be fair, one could neaten that with pipeline templates.

Also there is no syntax validation available as the configuration is defined in JSON and passed as a string within the Azure YAML pipeline. Another point where things can go wrong.

Finally I found it rather inconvenient when dealing with more than a single environment (dev, sprint, production).

"Okay, okay I got it...", you say. "Give me that solution!"

Solution

The solution is rather simple. For demonstration purpose let us ramp up a demo environment, by using the following bicep code.

var location = 'westeurope'

resource appcs 'Microsoft.AppConfiguration/configurationStores@2021-03-01-preview' = {
  name: 'appcs-demo'
  location: location
  sku: {
    name: 'free'
  }

  resource kv1 'keyValues' = {
    name: 'APPINSIGHTS_INSTRUMENTATIONKEY$production' 
    properties: {
      value: '<instrumentation_key>'
    }
  }

  resource kv2 'keyValues' = {
    name: 'SomeOtherKeyValuePair$production' 
    properties: {
      value: '<value_for_production>'
    }
  }

  resource kv3 'keyValues' = {
    name: 'SomeOtherKeyValuePair$development' 
    properties: {
      value: '<value_for_development>'
    }
  }
}

resource plan 'Microsoft.Web/serverfarms@2021-01-01' = {
  name: 'plan-appcs-demo'
  location: location
  sku: {
    name: 'B1'
    tier: 'Basic'
  }
}

resource appProduction 'Microsoft.Web/sites@2021-01-01' = {
  name: 'app-appcs-demo'
  location: location 
  properties: {
    serverFarmId: plan.id
  }
}

resource appDevelopment 'Microsoft.Web/sites@2021-01-01' = {
  name: 'app-appcs-demo-development'
  location: location 
  properties: {
    serverFarmId: plan.id
  }
}
Bicep - Demo environment

This will create two App Services (development, production) and an App Configuration store for us holding three key-value pairs.

Next you'll have to add the following Azure CLI task to your pipeline.

- task: AzureCLI@2
  displayName: 'Push config to development'
  inputs:
    azureSubscription: '<ARM_Service_Connection>'
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      ID=$(az webapp show --name app-appcs-demo-development --resource-group rg-appcs-demo --subscription <subscription> --query id --output tsv) 
      az appconfig kv export --name appcs-demo --destination appservice --label development --yes --appservice-account $ID

At its core we are using Azure CLI to export all key-value pairs carrying a label called development to the App Service named app-appcs-demo-development.

Conclusion

And that is all there is to it, simple and clean. Maintaining App Service settings now feals more conveniant while the configurational aspect is decoupled from the deployment logic. Also any changes made to a parameter will show up in the history and can be easily reverted.

As an added benefit it lets you quickly duplicate environmental settings into a new one by exporting to a new label.

Quickly clone settings from one environment to another

Oh, and did I mention that the smallest SKU of the App Configuration Service is sufficient that comes for free? 😀Sweet, isn't it 😎

Further reading

Azure App Service Settings task - Azure Pipelines
Azure App Service Settings Task supports configuring App settings, connection strings and other general settings in bulk using JSON syntax on your web app or any of its deployment slots.
Variable groups for Azure Pipelines and TFS - Azure Pipelines
Share common variables across pipelines using variable groups
Azure App Configuration documentation
Learn how to use Azure App Configuration, a managed service that helps developers centralize their application and feature settings simply and securely.
microsoft/azure-pipelines-tasks
Tasks for Azure Pipelines. Contribute to microsoft/azure-pipelines-tasks development by creating an account on GitHub.
Feature Toggles (aka Feature Flags)
Feature Flags can be categorized into several buckets; manage each appropriately. Smart implementation can help constrain complexity.