Syncing Feature Flags

Jan 22, 2023

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 .

#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
$DefaultEnabledUsersFilter = [PSCustomObject]@{
    Audience = [PSCustomObject]@{ 
        Users = $DefaultEnabledUsers
        Groups = @()
        DefaultRolloutPercentage = 0
    }
} | ConvertTo-Json

# 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 $DefaultEnabledUsersFilter -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...
  
powershell
Creative

Yasen Dinkov

Azure Static Web Apps and Content-Security-Policy