Azure DevOps - Change Release variable during execution
azure-devops releases
Changing and persisting variable values in Releases can be done using DevOps REST API and PowerShell tasks. This post describes how to do it.
June 8, 2020
Azure DevOps (ADO) has nice hierarchy of variables and dynamic values - you can define high-level variable groups and then specific variables which can be modified before starting a release. But what about being a little more dynamic and changing pipeline variables during execution? To be available for all stages?
Turns out that sharing information between stages in release pipeline is not as simple as it could be.
This post talks about the "classic" definition of pipelines where Builds and Releases are separate. YAML pipelines have more options of sharing data between stages.
tl;dr
To persist variable changes in a Release run (not definition):
- Set Project Collection Build Service permissions to Manage releases.
- Check the Allow scripts to access the OAuth token box.
- Use in PowerShell:
$url = "$(System.TeamFoundationServerUri)$(System.TeamProjectId)/_apis/Release/releases/$(Release.ReleaseId)?api-version=6.0-preview.8"
Write-Host "URL: $url"
$pipeline = Invoke-RestMethod -Uri $url -Headers @{
Authorization = "Bearer $(System.AccessToken)" # Provided by ADO thanks to OAuth checkbox
}
# Change the value of var2 to be persisted in the rest of the run.
$pipeline.variables.var2.value = "World"
# Alternatively create new variable in cases when it's not present.
# $pipeline.variables | Add-Member -MemberType NoteProperty -Name $name -Value @{value=$env.Value} -Force
$json = @($pipeline) | ConvertTo-Json -Depth 99
Invoke-RestMethod -Uri $url -Method Put -Body $json -ContentType "application/json" -Headers @{Authorization = "Bearer $(System.AccessToken)"}
The problem
The idea is to change a pipeline variable value during release stage execution and then consume this new value in the following stages.
- Pipeline variable
var2
is set to "Hello". - pre-deployment stage sets
var2
to "World". - actual deployment stage reads
var2
and gets "World".
Alternatively:
- Variable
var2
doesn't exist on the pipeline.- pre-deployment stage creates
var2
and sets value to "World".- actual deployment stage reads
var2
and gets "World".
Before running the pipeline:
If var2
is changed/created in pre-deployment the value will not be available in actual deployment.
The solution
Azure DevOps release pipelines offer a way to manipulate running release by using logging commands. One example is changing release name based on information available only during release - such as value of a pipeline variable.
Write-Host "##vso[release.updatereleasename]Deploy Infra - $("$(environment)".ToUpper()) - $(Release.ReleaseId)"
Running this in a PowerShell task will update the release name to something like this:
Bash on Linux agents works exactly the same (just echo instead of Write-Hosting).
We initially wanted to solve our problem by using another logging command: ##vso[task.setvariable variable=var2;]World
. That unfortunately doesn't work in this case, because variables set by a task are available only in the current stage - all subsequent tasks in "pre-deployment" can read it, but tasks in "actual deployment" can't.
There is a solution to this though: using the Azure DevOps REST API.
To set it up we first need to go to the Security page of the release definition.
And change the permissions of Project Collection Build Service to Allow - Manage releases:
To call the REST APIs we will need an access token. ADO can obtain and surface the token for us when we select the "Allow scripts to access the OAuth token" checkbox at the Agent job level in a particular stage (pre-deployment in this case):
And then finally use it in a PowerShell script:
$url = "$(System.TeamFoundationServerUri)$(System.TeamProjectId)/_apis/Release/releases/$(Release.ReleaseId)?api-version=6.0-preview.8"
Write-Host "URL: $url"
$pipeline = Invoke-RestMethod -Uri $url -Headers @{
Authorization = "Bearer $(System.AccessToken)" # Provided by ADO thanks to OAuth checkbox
}
# Change the value of var2 to be persisted in the rest of the run.
$pipeline.variables.var2.value = "World"
# Alternatively create new variable in cases when it's not present.
# $pipeline.variables | Add-Member -MemberType NoteProperty -Name $name -Value @{value=$env.Value} -Force
$json = @($pipeline) | ConvertTo-Json -Depth 99
Invoke-RestMethod -Uri $url -Method Put -Body $json -ContentType "application/json" -Headers @{Authorization = "Bearer $(System.AccessToken)"}
Attribution: This script was inspired by a solution presented at StackOverflow :) My colleague Sebastian Bader modified it to work with pipeline runs instead of definitions and made it a bit more sophisticated.
Feedback
Found something inaccurate or plain wrong? Was this content helpful to you? Let me know!
š§ codez@deedx.cz