Azure, Bicep, IaC, Pulumi

Deploying Feature Flags to Azure App Configuration

Introduction

In the world of Development, feature flags have become an indispensable tool for enabling Continuous Deployment and A/B testing. Azure App Configuration provides a centralised and scalable solution for managing application settings. In this blog post, we’ll explore how to deploy feature flags to Azure App Configuration using Infrastructure as Code (IaC) tools like Bicep and Pulumi, enhancing the control and flexibility of your deployment process.

Prerequisites

Before we dive into the deployment process, make sure you have:

  1. An Azure subscription
  2. An existing Azure App Configuration Service
  3. Basic knowledge of Azure App Configuration and feature flags concepts
  4. Bicep (0.20.4) and/or Pulumi CLI (3.79.0) installed
  5. Basic knowledge and understanding of Bicep and/or Pulumi concepts
  6. Azure CLI (2.51.0) installed

Documentation

Bicep App Configuration and Key Values

Pulumi App Configuration and Key Values

Step 1: Deploy Feature Flags

As we already have we have our App Configuration, let’s deploy a feature flag. For example, let’s create some flags named “newFeature” and “newFeature2” with both disabled. Here’s how you can do it:

Bicep

Create a .bicepparam file to store the configuration in e.g. features-dev.bicepparam

using 'features.bicep'
param configStoreName = 'appcs-myapp-dev-weu'
param featureFlags = [
  {
    name: 'newFeature'
    label: 'myteam'
    value: {
      id: 'newFeature'
      description: 'the new feature'
      enabled: false
    }
    tags: { 
      mytag: 'a feature tag'
    }
  }
  {
    name: 'newFeature2'
    label: 'myteam'
    value: {
      id: 'newFeature2'
      description: 'Another new feature'
      enabled: false
    }
    tags: {}
  }
]

And a Bicep file e.g. features.bicep to configure the Azure App Configuration feature flags

param configStoreName string
param featureFlags array

resource configStoreResource 'Microsoft.AppConfiguration/configurationStores@2023-03-01' existing = {
  name: configStoreName
}

resource configStoreKeyValue 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = [for entry in featureFlags: {
  parent: configStoreResource
  name: '.appconfig.featureflag~2F${entry.name}$${entry.label}'
  properties: {
    value: string(entry.value)
    contentType: 'application/vnd.microsoft.appconfig.ff+json;charset=utf-8'
    tags: entry.tags
  }
}]

Deploy using the Azure CLI

az deployment group create --name "flags-deploy" --resource-group rg-myapp-dev-weu --template-file features.bicep --parameters features-dev.bicepparam

Pulumi

Pulumi supports a number of languages but we will show TypeScript and C# examples for this post.

When creating a Pulumi project a stack is created and by default the stack is called dev and so a pulumi configuration file is created for that stack e.g. pulumi.dev.yaml. We can add our configuration for the feature flags in this configuration file.

encryptionsalt: v1:8c8dl9MgfYM=:v1:b+PWTv408Trh7uT6:1FlnAGfspnMZjosvoqvA+AjaGhWu2Q==
config:
  azure-native:location: WestEurope
  storeName: "appcs-myapp-dev-weu"
  resourceGroupName: "rg-myapp-dev-weu"
  featureFlags:
    - name: "newFeature"
      label: "myteam"
      value:
        id: "newFeature"
        description: "the new feature"
        enabled: false
      tags: 
        mytag: "a feature tag"
    - name: "newFeature2"
      label: "myteam"
      value: 
        id: "newFeature2"
        description: "Another new feature"
        enabled: false
      tags: {}

Typescript

import * as pulumi from "@pulumi/pulumi";
import * as appconfiguration from "@pulumi/azure-native/appconfiguration";

const config = new pulumi.Config();
const featureFlags = config.requireObject<any>("featureFlags");
const storeName = config.require("storeName")
const resourceGroupName = config.require("resourceGroupName")

featureFlags.forEach((entry: { name: string; label: string; tags: any; value: Object; }) => {
    new appconfiguration.KeyValue(`keyValue${entry.name}`, {
        configStoreName: storeName,
        keyValueName: `.appconfig.featureflag~2F${entry.name}$${entry.label}`,
        resourceGroupName: resourceGroupName,
        tags: entry.tags,
        contentType: "application/vnd.microsoft.appconfig.ff+json;charset=utf-8",
        value: JSON.stringify(entry.value)
    });
});

C#

using System.Collections.Generic;
using System.Dynamic;
using System.Text.Json;
using Pulumi;
using Pulumi.AzureNative.AppConfiguration;

return await Deployment.RunAsync(() =>
{
    Config config = new();
    var featureFlags = config.RequireObject<IReadOnlyList<ExpandoObject>>("featureFlags");
    var storeName = config.Require("storeName");
    var resourceGroupName = config.Require("resourceGroupName");
    
    foreach (dynamic entry in featureFlags) 
    {        
        new KeyValue($"keyValue{entry.name}", new()
        {
            ConfigStoreName = storeName,
            KeyValueName = $".appconfig.featureflag~2F{entry.name}${entry.label}",
            ResourceGroupName = resourceGroupName,
            ContentType = "application/vnd.microsoft.appconfig.ff+json;charset=utf-8",
            Tags = JsonSerializer.Deserialize<IReadOnlyDictionary<string, string>>(entry.tags, new JsonSerializerOptions { 
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            }),
            Value = JsonSerializer.Serialize(entry.value, new JsonSerializerOptions {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            })
        });
    }
});

Deploy using the Pulumi CLI

pulumi up

Step 2: Update Feature Flags

As the feature flags are now in code changing or adding a feature flag means updating the appropriate configuration file and then re-deploying either by a CLI as above or by a continuous deployment pipeline.

Conclusion

Deploying feature flags to Azure App Configuration using IaC (e.g. Bicep or Pulumi) enhances the reproducibility and scalability of your deployment process. Infrastructure as Code allows you to version and track changes effectively in source control. By following the steps outlined in this blog post, you can seamlessly integrate feature flags into your continuous deployment pipeline, enabling efficient feature rollout and experimentation.

Remember that these code snippets are just the beginning. As your application and feature flag requirements evolve, you can further customize and extend these configurations to suit your needs as well as run them in pipelines such as Azure Pipelines, GitHub Actions, etc..

Happy IaCing!!