Dynamic feature flags in Yaml

post

Overview

In this article, we use Azure CLI in the build pipeline to query the app configuration instance and pass whether a feature flag should be enabled to the ARM template.

Feature Flags

Using Feature Flags within a solution provides a great mechanism for rapid releases, they enable the developer to essentially release a piece of functionality that is switched off. There are many scenarios where this is helpful but one that I have come up against most recently is the audience are not ready to use the new functionality; this could relate to the new functionality requiring training or further comms but either way, this should not hold up the release.

With the introduction of feature flags, the code can be released and the feature branch merged into master. As you would expect, Martin Fowler says it better than me so please refer to his article for a more indepth look of Feaure Flags

Azure App Configuration

Step in Azure App Configuration, it provides a way to easily manage the flags and rapidly enable functionality. It can also be used to pilot functionality with Feature Flag Filters, a bit of a mouthful but essentially its possible to target the new functionality to a specific audience.

Provisioning

Its straight forward to provision the App Configuration resource in a standard ARM template, it’s a little trickier to dynamically add feature flags to the app configuration instance.

{
  "type": "Microsoft.AppConfiguration/configurationStores",
  "apiVersion": "2020-06-01",
  "tags": "[parameters('tags')]",
  "name": "[parameters('appConfigurationName')]",
  "location": "[resourceGroup().location]",
  "sku": {
    "name": "standard"
  },
  "properties": {
    "encryption": {}
  }
},
{
  "type": "Microsoft.AppConfiguration/configurationStores/keyValues",
  "apiVersion": "2020-07-01-preview",
  "name": "[concat(parameters('appConfigurationName'), '/.appconfig.featureflag~2FRemoveOldAccountsEnabled')]",
  "dependsOn": [
    "[parameters('appConfigurationName')]"
  ],
  "properties": {
    "enabled": "false",
    "contentType": "application/vnd.microsoft.appconfig.ff+json;charset=utf-8",
    "value": "[concat('{\"id\":\"RemoveOldAccountsEnabled\",\"description\":\"\",\"enabled\":\"false\",\"conditions\":{\"client_filters\":[]}}')]"
  }
}

NB: Its important to note that the forward slash needs to be encoded to "~2F" in the keyValues name

With the above template, the feature flag will be added in the disabled state, the problem I have noticed is that subsequent runs of the template provisioning process will disable the flag if it has been enabled since it was previously run.

Solution

To get around this, we can use Azure CLI in the build pipeline to query the app configuration instance and pass whether it should be enabled to the ARM template.

YAML

The following snippet of YAML can be integrated into your build pipeline tasks, in the scenario below I am using library variables to store the app config and subscription name.

# Gets and sets the feature flags
- task: AzureCLI@2
  displayName: 'Get App Configuration Feature Flags'
  inputs:
    azureSubscription: '${{ parameters.azureServiceConnection }}'
    scriptType: pscore
    scriptLocation: inlineScript
    inlineScript: |
      $featureFlags = az appconfig feature list -n $(AppConfigurationName) --subscription $(AzureSubscriptionId) --only-show-errors
      if ((($featureFlags | ConvertFrom-Json) | ? { $_.Key -eq 'RemoveOldAccounts'}).state -eq 'on') { $removeOldAccountsEnabled = $true } else {$removeOldAccountsEnabled = $false}
      Write-Output("##vso[task.setvariable variable=RemoveOldAccountsEnabled;]$($removeOldAccountsEnabled.ToString().ToLower())")

# Replaces any tokens in the parameters file
- task: qetza.replacetokens.replacetokens-task.replacetokens@3
  displayName: 'Replace Tokens'
  inputs:
    targetFiles: 'featureFlag-parameters.json'
    tokenPrefix: '#{'
    tokenSuffix: '}'

ARM

Now that we have the state of the feature flag being generated in the pipeline, we can inject it into the template as follows:

featureFlag-parameters.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "removeOldAccountsEnabled": {
      "value": "#{RemoveOldAccountsEnabled}"
    }
  }
}

featureFlag.json

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "removeOldAccountsEnabled": {
      "type": "string"
    }
  }
  "variables": {},
  "resources": [
    {
      "type": "Microsoft.AppConfiguration/configurationStores/keyValues",
      "apiVersion": "2020-07-01-preview",
      "name": "[concat(parameters('appConfigurationName'), '/.appconfig.featureflag~2FRemoveOldAccountsEnabled')]",
      "dependsOn": [
        "[parameters('appConfigurationName')]"
      ],
      "properties": {
        "enabled": "[bool(parameters('removeOldAccountsEnabled'))]",
        "contentType": "application/vnd.microsoft.appconfig.ff+json;charset=utf-8",
        "value": "[concat('{\"id\":\"RemoveOldAccountsEnabled\",\"description\":\"\",\"enabled\":',parameters('removeOldAccountsEnabled'),',\"conditions\":{\"client_filters\":[]}}')]"
      }
    }
  ]
}
Tim Hills

Tim Hills

Tim has been working in solution delivery for over 15 years and has really exceled in the industry. He has been fortunate enough to work with some high-profile clients and challenging projects which has positioned him well for turning business requirements into reality.

Registered office

Address: Arceau Solutions Ltd, Dane John Works, Gordon Rd, Canterbury, CT1 3PP

Telephone: 0208 191 7030