I have a requirement where I want a build pipeline run to wait if a previous run for the pipeline is still “inProgress”.
For example, in below image a run is already in progress, now a new run should wait or depend on this run to complete (either fail, cancel, or succeed).
I have explored below options:
- “exclusive lock” as a check in Environment. (not very flexible)
- invoke rest api as a check in Environment. (could not find much in Azure documentation on how to use result to handle check )
- or have a stage in yaml pipeline for checking previous runs status may be in a loop as a bash or python step. (this seems to be a flexible approach which can be customized.)
Any pointers on the right approach to address this requirement will really help.
As per Define approvals and checks – Exclusive Lock:
In the YAML file for the pipeline, specify a property called
lockBehavior
. This can be specified for the whole pipeline or for a given stage.
Example:
trigger: none
pool:
vmImage: 'ubuntu-latest'
lockBehavior: sequential
stages:
- stage: A
jobs:
- job: Job
steps:
- script: sleep 20
displayName: 'Sleep for 20 seconds'
Running 2 builds one right after another, this is displayed in the second build:
Clicking on View
button:
EDIT
I ran more tests. It seems that the lockBehavior
works only at the stage level, not the whole pipeline.
Consider the following pipeline with 2 stages:
trigger: none
pool:
vmImage: 'ubuntu-latest'
lockBehavior: sequential
stages:
- stage: A
jobs:
- job: Job
steps:
- script: sleep 20
displayName: 'Sleep for 20 seconds'
- stage: B
dependsOn: A
jobs:
- job: Job
steps:
- script: sleep 20
displayName: 'Sleep for 20 seconds'
Running 2 builds one right after another, we can see that stage A in the second build is run before stage B in the first build:
1
a new run should wait or depend on this run to complete
Currently, Azure DevOps doesn’t support it if your pipeline has multiple stages. Azure pipeline allocates agents based on jobs instead of builds. And the dependency is job/stage level instead of build level. If you want such feature, you can submit a feature request on Azure DevOps Developer Community and share the link here. Then others who want the same feature can follow it and add comments to increase the priority.
For your case, there is a workaround you can refer to, although it’s not perfect.
-
In the first stage of your pipeline, run REST API Definitions – Update to update the value of queueStatus to
paused
. When it ispaused
, builds in the queue will not be started by the system.- Run REST API Definitions – Get to check the current
queueStatus
, if it ispaused
, which means that there is already a running build, exit and fail the current build. - If the value is
enabled
, change it topaused
.
- Run REST API Definitions – Get to check the current
-
In the last stage of your pipeline, run REST API Definitions – Update to update the value of
queueStatus
toenabled
. -
You must use stage dependency to ensure the order of the step1 and step2.
-
Example YAML:
lockBehavior: sequential
pool:
vmImage: windows-latest
stages:
- stage: First
jobs:
- job: first_job
displayName: 'Pause new runs'
steps:
- task: PowerShell@2
inputs:
targetType: 'inline'
script: |
$token = "{PAT}"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($token)"))
$head = @{ Authorization =" Basic $token" }
$url="https://dev.azure.com/{OrgName}/{ProjectName}/_apis/build/definitions/{BuildDefinitionID}?api-version=7.1"
$response = Invoke-RestMethod -Uri $url -Method Get -Headers $head
if ($response.queueStatus -eq "enabled") {
$response.queueStatus = "paused"
Invoke-RestMethod -Uri $url -Method PUT -Headers $head -Body ($response | ConvertTo-Json -Depth 10) -ContentType application/json
} else {
exit 1
}
- stage: A
dependsOn: First
condition: succeeded()
jobs:
- job: A1
displayName: A1
steps:
- script: echo "This is A1"
- stage: B
dependsOn: A
condition: succeeded()
jobs:
- job: B1
displayName: B1
steps:
- script: echo "This is B1"
- stage: Last
jobs:
- job: last_job
displayName: 'Enable new runs'
steps:
- task: PowerShell@2
inputs:
targetType: 'inline'
script: |
$token = "{PAT}"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($token)"))
$head = @{ Authorization =" Basic $token" }
$url="https://dev.azure.com/{OrgName}/{ProjectName}/_apis/build/definitions/{BuildDefinitionID}?api-version=7.1"
$response = Invoke-RestMethod -Uri $url -Method Get -Headers $head
$response.queueStatus = "enabled"
Invoke-RestMethod -Uri $url -Method PUT -Headers $head -Body ($response | ConvertTo-Json -Depth 10) -ContentType application/json
-
Result:
- build1 gets the agent.
- build2 comes before build1 finishes the first stage. When build2 gets the agent, it will fail in the first stage. Build1 gets the agent back and keeps running until completing the last stage.
- build3 comes after Build1-Firststage finished, it will wait for the agent until build1 is completed.
4
Here is what worked for me.
stages:
- stage: ExistingBuildCheck
displayName: Checking if there is an existing build inProgress
jobs:
- job: GetInProgressBuilds
steps:
- checkout: none
- script: |
set -e
json_response=$(curl --location --request GET 'https://dev.azure.com/{Org}/{Project}/_apis/build/builds?definitions=$(System.DefinitionId)&branchName=$(Build.SourceBranch)&statusFilter=inProgress&api-version=7.1' --header 'Authorization: Bearer $(System.AccessToken)')
# escape character can create parsing issues
refined_json=$(echo $json_response| sed 's/\/\\/g')
## get count of all Runs retrieved where status is inProgress.
read -r count_inProgress_runs <<< $(python -c "import json; data = '''$refined_json'''; parsed = json.loads(data);
count_inProgress_runs = parsed['count'];
print(count_inProgress_runs )"
)
# exiting run counts as 1 so count > 1 confirms existing inProgress run other than the current run
if [ "$count_inProgress_runs " -gt 1 ]; then
echo -e "33[1;31m Another run is currently in progress. Wait for it to finish or cancel it before starting a new one."
exit 1 # exit with error to fail stage
else
echo -e "33[1;32m There is no other run inProgress except this one!"
exit 0 # success.
fi
The above stage is injected between build stage and deploy stage.
I allowed artifact build stage to complete, then a Rest API validation stage to check if any previous run status is inProgress
and fail the validation stage. User can first take decision on the already inProgress
run and after it completes then re-run the validation stage in current pipeline.