Limit Simultaneous Azure Pipelines Stage - azure-devops

I have an Azure DevOps Multi Stage Pipeline with multiple agents. Generally my stages consist of:
Build and Publish Artifacts
Deploy to environment 1
UI Test environment 1
Deploy to environment 2
UI Test environment 2
and so on for several environments. How do I allow simultaneous pipelines runs (e.g. Building and Publishing Artifacts for Build #2 while simultaneously UI Testing environment 2 for Build #1), while ensuring that no two agents will ever perform a UI Test for a given environment at the same time?
trigger: batch seems close to what I want, but I believe that disallows concurrency at the pipeline level, not at the stage level.

Your
UI Test environment 1 stage (specifically deployment job which Runs the tests) should target
env1uitest environment.
UI Test environment 2 stage (specifically deployment job which Runs the tests) should target
env2uitest environment.
Then set a exclusive lock ( https://learn.microsoft.com/en-us/azure/devops/release-notes/2020/pipelines/sprint-172-update#exclusive-deployment-lock-policy) policy on these two environments.
It should address your need:
"ensuring that no two agents will ever perform a UI Test for a given environment at the same time"
https://learn.microsoft.com/en-us/azure/devops/pipelines/process/approvals?view=azure-devops&tabs=check-pass#exclusive-lock

It seems that if somebody runs the release pipeline twice, You need it to run it one by one, not in parallel, right?
As a workaround:
Each release will start from stage 1. Thus we can add a PowerShell task as the first task for stage 1 to check if there are previous in-progress deployments.
In this PowerShell task, we can call this Rest API to check release stage status.
Power shell script:
# Base64-encodes the Personal Access Token (PAT) appropriately
$token = "$(pat)"
$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($connectionToken)"))
$success = $false
$count = 0
do{
try{
$stageurl2 = "https://vsrm.dev.azure.com/{org name}/{project name}/_apis/release/deployments?definitionId={release definition ID}&deploymentStatus=inProgress&api-version=6.0"
$stageinfo2 = Invoke-RestMethod -Method Get -ContentType application/json -Uri $stageurl2 -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
$inprogressdeployments = $stageinfo2.value | where {($_.deploymentStatus -eq "inProgress") -and ($_.release.name -ne $ENV:RELEASE_RELEASENAME) -and ($_.releaseEnvironment.name -ne 'stop services')} | Sort-Object -Property completedOn -Descending
#write-output $inprogressdeployments
$stageurl3 = "https://vsrm.dev.azure.com/{org name}/{project name}/_apis/release/deployments?definitionId={release definition ID}&operationStatus=QueuedForAgent&api-version=6.0"
$stageinfo3 = Invoke-RestMethod -Method Get -ContentType application/json -Uri $stageurl3 -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
$queueddeployments = $stageinfo3.value
#write-output $queueddeployments
if($inprogressdeployments) {
Write-output "Deployment In Progress, Waiting for it to finish!"
Write-output "Next attempt in 30 seconds"
Start-sleep -Seconds 30
} else {
Write-Host "No Current Deployment in Progress"
if($queueddeployments) {
write-output "Current Queued deployments"
Write-output "if 2 - Next attempt in 30 seconds"
Start-sleep -Seconds 30
}
else{
write-output "No Queued deployments, starting release"
$success = $true
}
}
}
catch{
Write-output "catch - Next attempt in 30 seconds"
write-output "1"
Start-sleep -Seconds 30
# Put the start-sleep in the catch statemtnt so we
# don't sleep if the condition is true and waste time
}
$count++
}until($count -eq 2000 -or $success)
if(-not($success)){exit}
Result:
Stage1 will continue to check until all previous versions are complete.
We could also try it with demands, we could specify same demands. Use demands to make sure that the capabilities your pipeline needs are present on the agents that run it. It won't run unless one or more demands are met by the agent.

You put the tasks into different jobs, and specify a dependsOn property for jobs that need to wait for particular job to be finished.
If you leave out the dependsOn, it will run synchronounsly.
After reading your prompt more closely, I think you just need a build stage that builds and publishes your artifact.
then a deploy_test stage for each environment. Stages run sequentially by default (unlike jobs)

Related

Prevent schedule from triggering when I have ongoing or failed builds in azure devops

In azure devops and yaml I can set a schedule trigger like this:
schedules:
- cron: "0 0 * * *"
displayName: Daily midnight build
branches:
include:
- main
And it will now trigger every night if main have new code since the last successful build.
But my problem is that I have builds that are long running with multiple stages (acc, int, prod) that we manually approve on different days.
So how do I prevent it from queueing new builds (with the same code) if it already have a build with x commit? Even it it's ongoing or in failed state. I can't seem to find anything in the documentation.
You can't accomplish this out of the box. However, you can write the logic into your pipeline.
To accomplish this, you would need to add a task into your pipeline that can:
Query for the last pipeline run
Evaluate if the current pipeline should proceed based on the last run's data
The step would look something like this:
- powershell: |
$header = #{ Authorization = "Bearer $env:System_AccessToken" }
$buildsUrl = "$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_apis/builds/builds"
$builds = Invoke-RestMethod -Uri $buildsUrl -Method Get -Header $header
$inProgressBuilds = $builds.value.Where({ ($.status -eq 'inProgress') -and ($_.definition.name -eq '$(Build.DefinitionName)') -and ($_.id -ne $(Build.BuildId)) })
if ( $inProgressBuilds.Count -gt 0 ) {
throw 'Pipeline run already in progress.'
}
displayName: "Validate Current Pipeline Runs"

Configuring Pipeline dependencies in Azure Pipelines

I have 2 Azure Pipelines, deploy and test. As their names imply one is used for deploying a product and the other is used for testing. When a developer wants to run their own tests on the existing deployment they trigger test. When a deployment is required they trigger deploy. If the test pipeline is in execution when the deploy pipeline is triggered I want the deploy to wait till the test has finished executing.
Is there a way to configure this dependency within the pipeline.yaml themselves, or a workaround to achieve the mentioned requirement
Is there a way to configure this dependency within the pipeline.yaml themselves, or a workaround to achieve the mentioned requirement
Here are two methods could meet your requirement:
1.You could add the Environment in your Yaml Pipeline. Add you could add Invoke Rest API check in the environment. Rest API: Latest - Get
In Yaml Pipeline, you could call this environment.
Example:
stages:
- stage: deploy
jobs:
- deployment: DeployWeb
displayName: deploy Web App
pool:
vmImage: 'Ubuntu-latest'
environment: 'EnvironmentName'
strategy:
runOnce:
deploy:
steps:
...
When you run the pipeline, the environment will check the latest build status of the test Pipeline. If the build has completed , it will run the deploy pipeline.
Result:
2.You could directly add a Powershell task in the Deploy task to check the status of the Test Pipeline.
$token = "PAT"
$url="https://dev.azure.com/{OrganizationName}/{ProjectName}/_apis/build/definitions/{DefinitionID}?includeLatestBuilds=true&api-version=5.1"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($token)"))
$response = Invoke-RestMethod -Uri $url -Headers #{Authorization = "Basic $token"} -Method Get -ContentType application/json
$buildid = $response.latestBuild.id
echo $buildid
$success = $false
do{
try{
$Buildurl2 = "https://dev.azure.com/{OrganizationName}/{ProjectName}/_apis/build/builds/$($buildid)?api-version=5.0"
$Buildinfo2 = Invoke-RestMethod -Method Get -ContentType application/json -Uri $Buildurl2 -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
$BuildStatus= $Buildinfo2.status
$result = $Buildinfo2.result
echo $result
echo $BuildStatus
if($BuildStatus -eq "completed" ) {
write-output "No Running Pipeline, starting Next Pipeline"
$success = $true
} else {
Write-output "Pipeline Build In Progress, Waiting for it to finish!"
Write-output "Next attempt in 30 seconds"
Start-sleep -Seconds 30
}
}
catch{
Write-output "catch - Next attempt in 30 seconds"
write-output "1"
Start-sleep -Seconds 30
# Put the start-sleep in the catch statemtnt so we
# don't sleep if the condition is true and waste time
}
$count++
}until($count -eq 2000 -or $success -eq $true )
if ($result -ne "succeeded" )
{
echo "##vso[task.logissue type=error]Something went very wrong."
}
if(-not($success)){exit}
You can also refer to my another ticket.
You will probably have to merge the pipelines into one and, depending on the steps in them, you can convert them to jobs or even to stages. In both cases, you can specify dependencies via dependsOn (e.g. docs for jobs).
So you will have something like:
jobs:
- job: test
steps:
- step1...
- step2...
- job: deploy
dependsOn: test
steps:
- step1...
- step2...
Also, if you go this way, consider using deployment jobs for deployment, they have some related build-in functionality.

Is there any way we can get task name in the release pipeline to execute specific task based on an condition

I'm working on a release pipeline there are around 3 task in the 1 Agent. taskA,taskB,taskC
I want to run specific task based on specific task failed. I tried custom condition but it didn't satisfied my case.
I want task C to be execute when only task B is failed for that I'm using output variable as well. In this case its working only for taskB. When TaskA failed Task B will skip and output variable become null in that case my task C is execute which is not correct.
I'm trying to make a condition which fulfill both TaskA and TaskB condition.
if TaskA failed --> TaskC should not run
if TaskB failed --> TaskC should run.
Here is my condition:-> and(eq(Agent.JobStatus, 'failed'), in(variables['oneboxout.oneboxvar'],'False'))
Is there any way we can get task name only so my work would be easier.
Below are my task screenshot for your reference.
Is there any way we can get task name only so my work would be easier.
The answer is yes.
We could use the REST API Releases - Get Release to get the task name or status;
https://learn.microsoft.com/en-us/rest/api/azure/devops/release/releases/get%20release?view=azure-devops-rest-6.0
As the above REST API, we need provide the current release Id to that REST API. To resolve this, we could use the REST API Releases - List with Parameter definitionId={definitionId} and powershell parameter Select-Object -first 1 to get the current release Id.
To resolve this request, I would like use following method:
Summary:
Add powershell a task (Let's call it Get JobB task result)between JobB and JobC to invoke REST API
to get the result of the JobB with condition Even if a previous task has failed, unless the build was canceled.
Set a variable RunJobC with different value based on the result of the task JobB in above powershell task.
Set condition and(always(), eq(variables['RunJobC'], 'True')) for the JobC.
My test scripts (Check Allow scripts to access the OAuth token option in the Phase):
$url = "https://vsrm.dev.azure.com/M<YourOrganization>/<YourProject>/_apis/release/releases?definitionId=24&api-version=6.0"
$RealeasePipeline= Invoke-RestMethod -Uri $url -Headers #{
Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"
} -Method Get
$ReleaseId= $RealeasePipeline.value.id | Select-Object -first 1
Write-Host The current Release Id: $ReleaseId
$url2 = "https://vsrm.dev.azure.com/<YourOrganization>/<YourProject>/_apis/release/releases/$($ReleaseId)?api-version=6.0"
$ReleaseInfo= Invoke-RestMethod -Uri $url2 -Headers #{
Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"
} -Method Get
$TargetTask=$ReleaseInfo.environments.deploySteps.releaseDeployPhases.deploymentJobs.tasks| where { $_.Name -eq "JobB"}
Write-Host JobB task Result is: $TargetTask.status
if ($TargetTask.status -eq "succeeded"){
Write-Host ("##vso[task.setvariable variable=RunJobC]False")
}elseif($TargetTask.status -eq "Failed"){
Write-Host ("##vso[task.setvariable variable=RunJobC]True")
}
My test result:

Skip a stage if the previous one has errors in Azure Pipeline Release

I've got a very simple Azure Pipeline Release, I just want to skip a stage if there are any errors in the previous one. I've already checked https://learn.microsoft.com/en-us/azure/devops/pipelines/process/conditions?view=azure-devops&tabs=classic
Setting the Test job to run "Only when all previous jobs have succeeded" doesn't help
My main goal is to skip the test stage whenever there's a particular condition in the previous one, and passing variables between stages doesn't seem to be possible, nor using the gates, so I've got to idea to deliberately raise an error in the stage. The stages run some PS scripts, and I can't make the whole stage fail from that either
Screen shot
The stages run some PS scripts, and I can't make the whole stage fail
from that either
Are you using PowerShell task or similar tasks? Try editing the task and enabling the Fail on Standard Error option:
After that task will fail if there's any error is written to the error pipeline. Also in pre-deployment conditions of your Test stage, make sure the trigger when ... checkbox is unchecked:
From what I understood you need sth like this feature. So as you see this is not supported out of the box.
You can use REST API to get state of running release and analyze response to verify it there is any issue with issueType = Error. Then in this script you need to call exit 1. This is not ideal, but it works.
$uri = "https://vsrm.dev.azure.com/thecodemanual/Devops manual/_apis/release/releases/$(Release.ReleaseId)?api-version=5.1"
Write-Host $(Release.ReleaseId)
Write-Host $uri
# Invoke the REST call
$result = Invoke-RestMethod -Uri $uri -Method Get -Headers #{Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"}
$errors = #()
$result.environments |
ForEach-Object { $_.deploySteps |
ForEach-Object { $_.releaseDeployPhases |
ForEach-Object { $_.deploymentJobs |
ForEach-Object { $_.tasks |
ForEach-Object { $errors += $_.issues | Where-Object { $_.issueType -eq "Error" } }}}}}
Write-Host $errors.Count
if($errors.Count -gt 0) {
Write-Host Error
exit 1
}
Without step above I got this:
And with this step this:

How do I push a NuGet package from Appveyor build only if all builds in matrix are successful?

I have a library with source code hosted on GitHub and configured to build on Appveyor CI (e.g. https://github.com/vostok/temp-library/blob/master/appveyor.yml).
I'd like to build and test this library on different platforms:
.NET Framework on Windows
.NET Core on Windows
.NET Core on Ubuntu
Naturally, I would configure a build matrix to build on different platforms. But then, I'd like to build and push a NuGet package only if all builds on all platforms were successful.
How do I configure something like this on Appveyor?
To run tests on Ubuntu and Windows, you also need to build before. So what you need to do is to make Windows job to wait for others (I am not sure which Windows job, as you have 2 of them but I believe you know).
So to make one matrix job wait for others, you need to do some scripting. Please use this sample as a reference.
write-host "Waiting for other jobs to complete"
$headers = #{
"Authorization" = "Bearer $env:ApiKey"
"Content-type" = "application/json"
}
[datetime]$stop = ([datetime]::Now).AddMinutes($env:TimeOutMins)
[bool]$success = $false
while(!$success -and ([datetime]::Now) -lt $stop) {
$project = Invoke-RestMethod -Uri "https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG" -Headers $headers -Method GET
$success = $true
$project.build.jobs | foreach-object {if (($_.jobId -ne $env:APPVEYOR_JOB_ID) -and ($_.status -ne "success")) {$success = $false}; $_.jobId; $_.status}
if (!$success) {Start-sleep 5}
}
if (!$success) {throw "Test jobs were not finished in $env:TimeOutMins minutes"}
If you have more than one concurrent job, you can make it wait longer. If you have only one concurrent job, make it wait less (because with one concurrent job when last job started, others already finished one way or another)
To make this script and Nuget deployment run only in specific Windows job, specialize matrix job configuration
$env:ApiKey you get at https://ci.appveyor.com/api-token and store as secure variable.