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:
- An Azure subscription
- An existing Azure App Configuration Service
- Basic knowledge of Azure App Configuration and feature flags concepts
- Bicep (0.20.4) and/or Pulumi CLI (3.79.0) installed
- Basic knowledge and understanding of Bicep and/or Pulumi concepts
- 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!!