Notice
I accidentally on a tangent here. You can just skip to the end if you just need the script to track features in Azure App Configuration.
The Problem
Feature toggling is tricky. If you are rolling out an update, you must hide it behind a feature flag. Feature toggles are highly-coupled to the code. Traditionally, you have an operations or DevOps team that can create and configure a feature flag before the code hits production.
However, feature flags and the underlying code are tightly knit. It would be ideal if the developer were in charge of creating and configuring the flag. Then, operators are only responsible for enabling and disabling it. This removes the communication overhead between configuring the flag and deploying the code.
Descaffolding
Releasing a new update or feature is often too risky to roll out blindly and comes with scaffolding:
-
Fixtures to roll out a risky update slowly to users.
-
Frameworks to A-B test a new feature and measure results
-
Configurable settings that you can only tune on production
-
Alerting and metrics that you only need for the migration period after deployment
-
Feature flags and preview-toggles
After the update is released successfully, we have all this dead code. It remains unused, but still out there creating bad variety .
The nature of scaffolding makes it tightly coupled to production code. It makes it hard to remove safely. Additionally, an ideal release is slow and continuous. It can sometimes be unclear when you finish releasing. At last, scaffolding lives in this gray area between disciplines. It’s part of testing, development, acceptance, and product feedback. So it often needs to be made clear who is responsible for it. In summary, scaffolding has three problems:
-
It’s hard to create and remove
-
It’s not clear when it’s unnecessary
-
Nobody is naturally responsible for it.
This result is scaffolding accruing over time and stacking up over the years. We’ve all seen products with dozens of toggles that conditionally enable behavior. This variety creates not only development overhead but possible unintended side effects.
The goal of the team should be to remove all this scaffolding. It’s like taking out the trash. This includes removing the conditional code and simultaneously removing the overarching toggle. Because the responsibility and initiation of this is so fuzzy it’s important to have a named process for it: Descaffolding.
Now you have an incentive and a enable all features. A priority to all users and standardize your solution. We cannot think of a feature as being complete before it’s been descaffolded.
Azure App Configuration
I’ve been using Azure App Configuration to centralize settings and configs across a cloud ecosystem. It’s got a fantastic integration with Azure Key Vault that makes configuring secrets a breeze. But the topic of this blog is using the built-in feature flag manager to create and delete new features together with your code.
Feature flags are a great way to manage your releases. They enables users to safely enable or disable features at runtime without having to
Synchronising Feature flags
The script below synchronizes features found on a target App Configuration resource with the $Flags
you pass in the parameter.
You can also pass it a list of $DefaultEnabledUsers
; this will create a default feature filter using App Configuration’s built-in conditional access
.
Thanks to my colleague for pointing out the escaping shenanigans that happen with azure cli’s json commands.
#SyncFeatureFlags.ps1
param (
[string] $TargetConfiguration,
[string] $FlagsPath,
[string] $DefaultEnabledUsersPath
)
$Flags = Get-Content $FlagsPath
$DefaultEnabledUsersFile = Get-Content $DefaultEnabledUsersPath
#Get all feature flags from production
$ExistingFlags = az appconfig feature list -n $TargetConfiguration --query '[].name' | ConvertFrom-Json
#Left Delta
$ToBeCreated = $Flags | Where-Object -FilterScript {($_ -notin $ExistingFlags ) }
#Right Delta
$ToBeDeleted = $ExistingFlags | Where-Object -FilterScript {($_ -notin $Flags ) }
Write-Host "Creating:"
Write-Host $ToBeCreated
Write-Host "Deleting:"
Write-Host $ToBeDeleted
# Add all demo customers to the feature by default
# + Some escaping shenanigans
$FilterJson =
[PSCustomObject]@{
Users = @()
Groups = @()
DefaultRolloutPercentage = 0
}| ConvertTo-Json -Compress
$Filter = 'Audience=' + $FilterJson.replace('"','\"')
# Create flags and set default filter
foreach($Flag in $ToBeCreated){
az appconfig feature set -n $TargetConfiguration --feature $Flag -y -o none
az appconfig feature filter add -n $TargetConfiguration --feature $Flag --filter-name "Microsoft.Targeting" --filter-parameters $Filter -y -o none
Write-Host "Created $Flag"
}
#Delete flags
foreach($Flag in $ToBeDeleted){
az appconfig feature delete -n $TargetConfiguration --feature $Flag -y
}
Example
Given a list of features on https://contoso-conf.azconfig.io
:
- Flag1
- Flag2
- Flag3
- Flag4
And a list of features in this new release’s features.txt
:
Flag1
Flag2
Flag3
Flag5
And these users id’s in the new release’s default-users.txt
b6fdf9eb-75ca-4c2d-aa47-c3876ed73871
0295d43c-7c6e-46c3-90f2-c5e2e81a0992
a288a208-e651-4099-b286-cf885999ec1a
78198dbf-31a2-4e7f-909f-2ccc1dc97686
d72cd2c2-1ebb-4962-9701-039f14e089bd
Then runnning:
.\SyncFeatureFlags.ps1 -TargetConfiguration 'contoso-conf' -FlagsPath '.\features.txt' -DefaultEnabledUsersPath '.\default-users.txt'
Will delete Feature4 and create Feature5 enabling it for your default users.
Now all these files can live in your VCS and managed by PR.
Here’s an azure pipelines yaml:
#... Some build stage
- stage: Release
dependsOn: Build
jobs:
- deployment: Deploy
environment: Azure-Subscription
pool:
vmImage: ubuntu-latest
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: 'deployment-scripts'
- task: AzureCLI@2
displayName: 'Syncronise Feature Flags'
inputs:
azureSubscription: 'Production'
scriptType: 'pscore'
scriptLocation: 'scriptPath'
scriptPath: 'SyncFeatureFlags.ps1'
arguments: ' -TargetConfiguration 'contoso-conf' -FlagsPath '.\features.txt' -DefaultEnabledUsersPath '.\default-users.txt'
#Some more stuff...