Versioned Azure App Configuration
Overview
App Configuration is a great way to store application configuration, it brings together both configuration, secrets and feature flags in a performant centralised manor.
This article covers an approach to use App Configuration with deployment slots so an application can be test against a staging slot and staged configuration before going live.
Versioned Backend
The first thing that I find useful for this process is to create a versioning strategy that works for your needs, the approach we often adopt is {MAJOR}.{MINOR}.{BUILD} which is Semantic Versioning. There is no need to snapshot each build version in App Configuration so {Major}.{Minor} works for this purpose.
Program.cs
The key part to note here is the '.Select(KeyFilter.Any, packageVersion);', this means that when the backend loads it will get the assembly versioned configuration values from App Configuration:
builder.Configuration.AddAzureAppConfiguration(options =>
{
var programAssembly = typeof(Program).Assembly;
var fileVersionInfo = FileVersionInfo.GetVersionInfo(programAssembly.Location);
var packageVersion = $"{fileVersionInfo.ProductMajorPart}.{fileVersionInfo.ProductMinorPart}";
var connectionString = builder.Configuration.GetValue<string>("APP_CONFIGURATION_CONNECTION_STRING_PROPERTY");
var tenantId = builder.Configuration.GetValue<string>("AAD_TENANT_ID_PROPERTY");
options.Connect(connectionString).ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential(
new DefaultAzureCredentialOptions
{
InteractiveBrowserTenantId = tenantId,
SharedTokenCacheTenantId = tenantId,
VisualStudioCodeTenantId = tenantId,
VisualStudioTenantId = tenantId
}));
})
.Select(KeyFilter.Any, packageVersion);
});
Update Version in Pipelines
For this to work well there needs to be proper versioning throughout your pipelines, whatever the approach, it needs to be
- task: DotNetCoreCLI@2
displayName: "DOTNET BUILD"
inputs:
command: publish
publishWebProjects: false
projects: "$(System.DefaultWorkingDirectory)/SOME_PROJECT/SOME.sln"
arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory) /p:Version=$(MinorVersion)"
zipAfterPublish: true
In the above instance, the $(MinorVersion) is determined elsewhere using git commands as demonstrated below:
$build = $env:REVISION
$env = $env:BUILDSUFFIX
$source = "$(Build.SourceBranch)"
if($source.indexOf('sources/tags') -gt -1){
$sources = $source.split('/')
$incrementedVersion = [version]::parse($sources[($sources.length - 1)])
$tagMessage = git tag -l $source.substring(10) --format='%(contents)' $BITRISE_GIT_TAG
} else {
$gitTags = git tag --sort=-creatordate
$tagMessage = $(git tag -n --sort=-creatordate --format='%(contents)' $BITRISE_GIT_TAG)[0]
if($gitTags[0].indexOf('/') -gt -1){
$tag = $gitTags[0].split('/')[1]
$incrementedVersion = [version]::parse($tag)
} else {
$tag = $gitTags[0]
$version = [version]::parse($tag)
$incrementedVersion = [version]::new($version.Major, ($version.Minor + 1), $buildNumber)
}
}
echo "##vso[task.setvariable variable=MinorVersion;isOutput=true]$($incrementedVersion.Major).$($incrementedVersion.Minor)"
App Configuration
By this point, the backend is versioned effectively and expecting version configuration to exist in App Configuration, this means its required to start adding the version during the Azure infrastucture deployments, the following is an example in Bicep:
resource appConfigurationResource 'Microsoft.AppConfiguration/configurationStores@2022-05-01' = {
tags: {}
name: 'APP_CONFIG_NAME'
location: 'West Europe'
sku: {
name: 'Basic'
}
identity: {
type: 'SystemAssigned'
}
properties: {
encryption: {
}
}
}
resource appConfigKeyValue 'Microsoft.AppConfiguration/configurationStores/keyValues@2022-05-01' = {
name: 'MY_CONFIG_ITEM$3.3'
parent: appConfigurationResource
properties: {
value: 'Some config'
}
}]
This means that in App Configuration, the configuration values will all be nested by the version as in the image at the top.
Slot Switch
All of this means that the latest release can be deployed to a staging slot of an App Service, validated in a safe manor and then switched to production once confident.
- task: AzureCLI@2
displayName: 'SWAP SLOT'
inputs:
azureSubscription: 'CONNECTION_NAME'
scriptType: pscore
scriptLocation: inlineScript
inlineScript: |
az account set --subscription $(SUBSCRIPTION_ID)
az webapp deployment slot swap -g $(RESOURCE_GROUP) -n $(APP_SERVICE_NAME) --slot staging --target-slot production