Getting parameters out of ARM/BICEP Deployments

The Issue

Historically, we have used Kees Schollaart’s ARM Outputs Azure DevOps task to convert the output from an ARM template deployment into a variable that can be used in a subsequent Azure DevOps pipeline task, using the general form

- task: AzureResourceManagerTemplateDeployment@3
  displayName: Deploy the main template
  inputs:
    deploymentScope: 'Resource Group'
    azureResourceManagerConnection: 'ARMConnEndpoint'
    subscriptionId: '$(SubscriptionId)'
    action: 'Create Or Update Resource Group'
    resourceGroupName: '$(ResourceGroup)'
    location: '$(AzureRegion)'
    templateLocation: 'Linked artifact'
    csmFile: '$(Pipeline.Workspace)/ARMtemplates/azuredeploy.json'
    overrideParameters: >-
      -staticSitelocation "westeurope"
      -projectName "$(projectName)"
      -env "$(environment)"      
    deploymentMode: 'Incremental'

- task: ARM Outputs@6
  displayName: Obtain outputs from the deployment of the Main Deploy
  name: 'MainDeployOutput'
  inputs:
    ConnectedServiceNameSelector: 'ConnectedServiceNameARM'
    ConnectedServiceNameARM: 'ARMConnEndpoint'
    resourceGroupName: '$(ResourceGroup)'
    whenLastDeploymentIsFailed: 'fail'

This process has been working well until we upgraded our service connections to workload identity federation. As soon as we did this the ARM Outputs@6 task started failing with the error message

Logging in using ApplicationTokenCredentials, authScheme is ‘WorkloadIdentityFederation’
Unhandled exception during ARM Outputs Task Error: secret must be a non empty string.

A quick look on the task’s GitHub repository showed we were not alone in seeing this error, but also that the task had not been updated in a good while.

To Fork or not to Fork

This task, like so many others in the Azure DevOps marketplace, has ceased to be under active support.

Our first thought was to fork the repository and fix the issue ourselves. However, after a bit of research we found the fix was non-trivial and would probably need a complete swap out of the authentication libraries to update to ones that supported WorkloadIdentityFederation as the existing libraries were deprecated.

However, more than the work involved, there was also the issue of what to do with any fix. We could see that, as the task was no longer under support, no PRs had been merged into the original repo for a number of years. So we had no mechanism to get our fix out to the community via the original GitHub repo and the Azure DevOps Marketplace listing. Our only option was republishing the task under a new ID. Not an easily discoverable solution or desirable as this confuses the marketplace and makes it harder for users to find the right task.

Also we did not really have the appetite to maintain the fork of the task, and keep it up to date with the latest changes in the Azure DevOps API. This lack of support commitment is just the problem that got us to the current position.

The Solution

The solution was a pattern we find ourselves using more and more i.e. Don’t create an Azure DevOps task and publish it via the Azure DevOps Marketplace, but rather use a generic script task and some BASH/PowerShell to get the job done within the code the team has direct access too in their repos. Whether it be a project repo or some shared internal repo that is used by many projects.

So the above pipeline was replaced with the following

- task: AzureResourceManagerTemplateDeployment@3
  displayName: Deploy the main template
  inputs:
    deploymentScope: 'Resource Group'
    azureResourceManagerConnection: 'ARMConnEndpoint'
    subscriptionId: '$(SubscriptionId)'
    action: 'Create Or Update Resource Group'
    resourceGroupName: '$(ResourceGroup)'
    location: '$(AzureRegion)'
    templateLocation: 'Linked artifact'
    csmFile: '$(Pipeline.Workspace)/ARMtemplates/azuredeploy.json'
    overrideParameters: >-
      -staticSitelocation "westeurope"
      -projectName "$(projectName)"
      -env "$(environment)"      
    deploymentMode: 'Incremental'
    deploymentOutputs: deploymentOutputs # need this parameter adding to get the outputs as a JSON string in an environment variable

# Then process the outputs from the ARM deployment and promote them to pipeline variables
- task: PowerShell@2
  displayName: Obtain Azure Deployment outputs
  inputs:
    targetType: 'inline'
    script: |
      if (![string]::IsNullOrEmpty( '$(deploymentOutputs)' )) {
        $DeploymentOutputs = convertfrom-json '$(deploymentOutputs)'

        $DeploymentOutputs.PSObject.Properties | ForEach-Object {
            $keyname = $_.Name
            $value = $_.Value.value
            Write-Host "The value of [$keyName] is [$value]"
            Write-Host "##vso[task.setvariable variable=$keyname]$value"
        }
      }      

Updated 23rd July In the inline PowerShell the $(deploymentOutputs) variable syntax is required, as opposed to the $env:deploymentOutputs syntax, if you are using an Ubuntu agent. So it is probably best to always use the $(deploymentOutputs) syntax to ensure cross platform compatibility.

This solution works, is maintainable and is under our control. Hope the same YAML unblocks some other teams out there.