I have run into some difficulties with a deployed azure web api. I have the deployment pipeline set up in 5 stages: ci, dev, test, acc, prod. I then have the infrastructure deploy job AzureResourceManagerTemplateDeployment
(in the dev/test/acc/prod stage) output a storage uri. Then use DownloadPipelineArtifact
, replacetokens
and AzureWebApp
tasks to deploy.
The configuration is added to the application like so:
private static void AddConfig(this WebApplicationBuilder builder, string fileName)
=> builder.Configuration
.AddJsonFile($"{configDir}/{fileName}.json", optional: false, reloadOnChange: true)
.AddJsonFile($"{configDir}/{fileName}.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
I have verified in the deployed files that the token is correctly replaced in the json file, yet when I send a request to a debugging endpoint I made (just to return the configured uri) it presents me with #{StorageUri}#
.
Debug controller for reference:
public class DebugController(IOptions<StorageSettings> settings) : BaseController
{
private readonly IOptions<StorageSettings> _settings = settings;
[HttpGet("Get")]
public async Task<string> Get()
{
return _settings.Value.StorageUri;
}
}
My best guess is that the configuration jsons are no longer used after compiling, and the dll will have the value from the file already. I do not wish to build the application for every environment individually, but rather download the artifact for each stage and replace the tokens before deploying. Is there any way to achieve it in this fashion? I have also tried using IConfigurationRoot.Reload()
but to no avail.
EDIT: The executed pipeline yml is as follows:
trigger:
branches:
include:
- master
paths:
include:
- ${appname}$/src/**
exclude:
- ${appname}$/tests/**
pr:
branches:
include:
- master
pool:
vmImage: 'windows-latest'
stages:
- stage: CI
displayName: "Integration"
jobs:
- job: BuildTestPublish
displayName: "Build and test the application, then publish the artifact."
steps:
- task: DotNetCoreCLI@2
displayName: "NuGet Restore"
inputs:
command: "restore"
projects: |
${appname}$/*.sln
- task: DotNetCoreCLI@2
displayName: "Build and publish the Artifact"
inputs:
command: "publish"
publishWebProjects: false
projects: |
${appname}$/*.sln
arguments: "-o $(Build.SourcesDirectory)/Output -c Release"
zipAfterPublish: false
- task: VSTest@2
displayName: "Run Automated Tests"
inputs:
testAssemblyVer2: |
**\*.UnitTests.dll
**\*.IntegrationTests.dll
platform: "$(buildPlatform)"
configuration: "$(buildConfiguration)"
searchFolder: "$(Build.SourcesDirectory)/Output"
- task: PublishBuildArtifacts@1
displayName: "Publish the build artifacts"
inputs:
PathtoPublish: "$(Build.SourcesDirectory)/Output"
ArtifactName: "${appname}$"
- stage: cdDev
displayName: Deploy to Development
variables:
- name: envName
value: dev
- name: serviceConnectionName
value: ${service_connection}$
- name: resourceGroupName
value: ${resource_group}$
dependsOn:
- CI
condition: succeeded()
jobs:
- job: infraDeploy
displayName: "Deploy Infrastructure"
steps:
- task: AzureResourceManagerTemplateDeployment@3
inputs:
connectedServiceName: ${service_connection}$
deploymentName: $(Build.Buildnumber)
location: ${location}
resourceGroupName: $(resourceGroupName)
csmFile: infrastructure/main.bicep
csmParametersFile: infrastructure/main.bicepparam
overrideParameters: |
-environmentSuffix $(envName)
deploymentOutputs: deploymentOutputs
- task: PowerShell@2
name: "outputVariables"
displayName: "Set Deployment Output Variables"
inputs:
targetType: inline
script: |
$armOutputObj = '$(deploymentOutputs)' | ConvertFrom-Json
$armOutputObj.PSObject.Properties | ForEach-Object {
$keyname = $_.Name
$value = $_.Value.value
Write-Output "##vso[task.setvariable variable=$keyName;issecret=false;isOutput=true]$value"
Write-Output "Output variable: $keyName"
Write-Output "Output value: $value"
}
pwsh: true
- job: softwareDeploy
displayName: "Deploy Software"
dependsOn:
- infraDeploy
variables:
- name: storageUri
value: $[ dependencies.infraDeploy.outputs['outputVariables.storageUri'] ]
steps:
- task: DownloadPipelineArtifact@2
inputs:
artifactName: "${appname}$"
path: "$(Pipeline.Workspace)/${artifactname}$"
- task: replacetokens@6
displayName: "Replace Tokens"
inputs:
sources: |
$(Pipeline.Workspace)/${artifact_name}$/appsettings*.json
$(Pipeline.Workspace)/${artifact_name}$/Configurations/*.json
tokenPattern: "custom"
tokenPrefix: "#{"
tokenSuffix: "}#"
addBOM: true
missingVarAction: "keep"
missingVarLog: "error"
ifNoFilesFound: "error"
- task: AzureWebApp@1
inputs:
azureSubscription: ${service_connection}$
appName: ${appname}$-app-$(envName)
package: "$(Pipeline.Workspace)/${artifact_name}$"
Cut the test, acc, and prod stage as they are repeats of dev with different values, and replaced some info with ${value}$
tokens.
Solved: the issue was not with the token replacement or config reading, but rather with the class the json was bound to.
public string storageUri = "#{storageUri}#";
is correctly replaced during startup, but not after injection.
public string storageUri { get; set; } = "#{storageUri}#"
is correctly replaced every time. Thanks to Miao Tian-MFST for helping me find this.