In an Azure Devops pipeline, how can I detect and cancel other build jobs from the same Git branch? - powershell

How do I write an Azure Pipeline script that detects whether any other CI build jobs are running using the same Git branch, and cancels those other jobs?
I want to cancel only CI build jobs. Any PR build jobs and manually triggered jobs from the same Git branch should be ignored, and allowed to continue running.
Any build jobs from other Git branches should also be ignored.
The Azure DevOps VM is a self-hosted Windows VM, so the task must be a PowerShell or Windows script, not bash. The source is in Bitbucket Cloud -- this is important, because ADO handles Bitbucket Cloud repositories differently from other repositories.
If a canned task is available, I can use it as well.
The following questions are related, but they do not directly address this use case.
Is an Azure DevOps build pipeline, is there a way to cancel one pipeline job from another job?
Azure devops build pipeline depends on other build pipeline

You can first use the API "Builds - List" to list all the builds which have been trigged but not completed.
GET https://dev.azure.com/{organization}/{project}/_apis/build/builds?reasonFilter={reasonFilter}&statusFilter={statusFilter}&branchName={branchName}&repositoryId={repositoryId}&api-version=6.0
For your case,
The value of reasonFilter should be batchedCI and individualCI.
The value of statusFilter should be inProgress, notStarted and postponed.
The value of branchName is the branch you specify.
The value of repositoryId is the ID of your Git repository.
Then use the API "Builds - Update Build" to cancel all the builds (except the current build) in a loop.

You can add powershell script step into your build definition to check active builds on the same branch. As an example
$user = ""
$token = "$(System.AccessToken)"
$buildDef = "$(System.DefinitionId)"
$branchName = "$(Build.SourceBranch)"
$teamProject = "$(System.TeamProject)"
$orgUrl = "$(System.CollectionUri)"
$buildId = $(Build.BuildId) -as [int]
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$uriGetActiveBuilds = "$orgUrl/$teamProject/_apis/build/builds?definitions=$buildDef&statusFilter=inProgress&branchName=$branchName&api-version=5.1"
$resultStatus = Invoke-RestMethod -Uri $uriGetActiveBuilds -Method Get -ContentType "application/json" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
if ($resultStatus.count -gt 0)
{
foreach ($build in $resultStatus.value)
{
$bid = $build.id -as [int]
if ($buildId -gt $bid) //if exists a lower value of the build id on the same branch, the current build should be stoped
{
exit 1
}
}
}

The answer from #Shamrai-Alexsandr cancels the current build, but what I want to do was cancel all other builds (that is, CI builds on the current branch) still in progress.
The answer from #bright-ran-msft gave me enough clues to combine #bright's solution with #shamrai's solution, replacing the exit 1 with code that cancels the other builds:
if ($buildId -gt $bid)
{
$build.status = "Cancelling"
$cancelRequest = $build | ConvertTo-Json -Depth 10
$uriCancel = "$orgUrl$teamProject/_apis/build/builds/$($build.id)?api-version=6.0"
$resultOfCancel = Invoke-RestMethod -Uri $uriCancel -Method Patch -ContentType "application/json" -body $cancelRequest -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
Write-Host "Result of Cancel request: " $resultOfCancel.status
}

Related

Azure DevOps Server - pause/resume all pipelines during maintenance window

We have an Azure DevOps Server (on prem) and different build/release pipelines.
A build/release is depending to other systems. Now if we plan to do maintenance work on this other systems, no Azure build/release pipeline should be run during this time because of the dependency of this systems.
We can go to every pipeline and set the pipeline to "pause". This is working well for a small numbers of build/release pipelines, but if we have a lot of pipelines this would be time-consuming to enabled-disable all pipelines.
Is there any way to pause/resume all Azure Pipelines at the same time? (e.g. TeamCity has a simple flag to pause/resume the whole queue).
I checked the API, but there is also no way to disable the queue itself (change setting on the build/release pipeline). It this would be possible, we could loop through every pipeline definition and pause/resume the queue.
You can disable the agents to prevent the pipeline from running.
Go the Agent Pools under Pipelines in Project settings-->Select the agent pool -->Go to Agents tab-->Disable all the agents.
You can also use rest api to pause the build pipeline mentioned in the comments. See below example in powershell scripts: See here to get a personal access token.
$PAT="Personal Access Token"
$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($PAT)"))
#list build definiations api
$url = "https://dev.azure.com/org/proj/_apis/build/definitions?api-version=6.1-preview.7"
#list all build definitions
$AllDefinitons = Invoke-RestMethod -Uri $url1 -Headers #{Authorization = ("Bearer {0}" -f $base64AuthInfo)} -Method get
#get all the definition ids
$Ids = $AllDefinitons.value | select id
foreach($id in $Ids){
$definitionUrl="https://dev.azure.com/org/proj/_apis/build/definitions/$($id.id)?api-version=6.1-preview.7"
#get the definition of each build pipeline
$definiton = Invoke-RestMethod -Uri $definitionUrl-Headers #{Authorization = ("Bearer {0}" -f $base64AuthInfo)} -Method get
#set queueStatus to paused
$definiton.queueStatus= "paused"
#update the definition
Invoke-RestMethod -Uri $definitionUrl-Headers #{Authorization = ("Basic {0}" -f $base64AuthInfo)} -Method put -Body (ConvertTo-Json $definiton-Depth 100) -ContentType "application/json"
}

How can you programmatically raise a conditional PR?

We currently have the following branch strategy:
Develop > Release > Master
Devs create a local branch from Develop, make changes merge to develop. Our dev environment builds off that branch. When they want their changes tested they push to QA environment, which also uses the Develop branch. This cycle goes on until functional testing for the iteration is done. At that point the code is merged into the Release branch and is deployed through staging and then to prod. After deploying to prod the code should be merged to Master, but it's often forgotten about. This causes problems in niche scenarios.
Is there a way to use perhaps devops pipelines to conditionally raise a PR automatically? So I'm thinking this would need 2 PRs:
PR raised for Master branch after successful release to prod. Idea here would be once sign off has been granted someone from the team can just approve.
PR raised for Develop branch if the first PR is approved and the code in Master now differs from Develop. In a lot of cases it won't as therefore wouldn't need a PR.
I've been googling for this and found the api methods like this but I can't see how you could put this in a pipeline and make it conditional.
Additional Info:
My understanding is that the build definition needs to know what branch to build as per the image below. So, creating a new release branch every sprint would either result in having to update the build definition every time or creating a new build definition, that essentially would be a complete replica in most cases except for the branch name. Unless I'm misunderstanding, which I think I am.
The following PowerShell example creates a pull request from master to some target branch as a build task. You can add it to your release as an additional stage "Create PR to some branch" with one PowerShell task. Additionally, you can create periodically build to check diff and create pull requests.
$user = ""
$token = "$(System.AccessToken)"
$branchTarget = "$(Build.SourceBranch)"
$branchSource = "refs/heads/master"
$branchTragetPath = $branchTarget -replace "refs/heads/", ""
$teamProject = "$(System.TeamProject)"
$repoName = "$(Build.Repository.Name)"
$orgUrl = "$(System.CollectionUri)"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$uriBranchStatus = "$orgUrl/$teamProject/_apis/git/repositories/$repoName/stats/branches?name=$branchTragetPath&api-version=5.1"
$uriCheckActivePR = "$orgUrl/$teamProject/_apis/git/repositories/$repoName/pullrequests?searchCriteria.targetRefName=$branchTarget&searchCriteria.sourceRefName=$branchSource&api-version=5.1"
$uriCreatePR = "$orgUrl/$teamProject/_apis/git/repositories/$repoName/pullrequests?api-version=5.1"
$resultStatus = Invoke-RestMethod -Uri $uriBranchStatus -Method Get -ContentType "application/json" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
if ($resultStatus.behindCount -eq 0)
{
Write-Host "Current branch contains last changes from master"
Return
}
$resultActivePR = Invoke-RestMethod -Uri $uriCheckActivePR -Method Get -ContentType "application/json" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
if ($resultActivePR.count -gt 0)
{
Write-Host "PR exists already"
Return
}
$bodyCreatePR = "{sourceRefName:'$branchSource',targetRefName:'$branchTarget',title:'Sync changes from $branchSource'}"
$result = Invoke-RestMethod -Uri $uriCreatePR -Method Post -ContentType "application/json" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $bodyCreatePR
Write-Host "Created PR" $result.pullRequestId

Azure Devops - How to call one pipeline from another

Could someone help me with how to call one pipeline from another pipeline in Azure DevOps?
I have to run a pipeline and this should trigger another pipeline in different project.
You can try to use Trigger Azure DevOps Pipeline task to trigger another pipeline in different projects.
Depending on your choice in the task it will trigger a build or a release pipeline.
To be able to use the extension an Azure DevOps API endpoint needs to be created.
For the service connection to work as it should you need to configure the following parameters:
Organization Url: The URL of the organization.
(https://dev.azure.com/[organization])
Release API Url: The URL of the Azure DevOps Release API
(https://vsrm.dev.azure.com/[organization])
Personal Access Token: The personal access token.
How you can create a personal access token can be found here: Use personal access tokens to authenticate.
Make sure the personal access token has the following rights:
Triggering a Release: Release – Read, write & execute – Build Read &
Execute
Triggering a Build: Read & Execute
I think resources for Azure Pipelines is what you looking for.
Add a resource in the pipeline that shall be called from another one and name the source pipeline:
# Explicitly set none for repository trigger
trigger:
- none
resources:
pipelines:
- pipeline: myappbuild # Name of the pipeline resource
source: myapp-build-pipeline # Name of the triggering pipeline
trigger:
branches:
- master
You can use API for triggering build. Here is the example that I use to trigger another build pipeline.
- powershell: |
# Write your PowerShell commands here.
Write-Host " ***** Start Script ***** "
$body = '
{
"parameters": "{\"parameter1\": \"value1\"}",
"definition": {"id": "1234"},
"sourceBranch": "git/branch",
"templateParameters": {"templateparameter": "paramvalue"}
}
'
$bodyJson=$body | ConvertFrom-Json
Write-Output $bodyJson
$bodyString=$bodyJson | ConvertTo-Json -Depth 100
Write-Output $bodyString
$user="$(user)"
$token="$(token)"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$Uri = "https://tfs.com:8443/Organization/_apis/build/builds?api-version=6.1-preview.6"
$buildresponse = Invoke-RestMethod -Method Post -UseDefaultCredentials -ContentType application/json -Uri $Uri -Body $bodyString -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
write-host $buildresponse
$buildID = $buildresponse.id
write-host $buildID
Write-Output "Build ID is $buildID... Sleep for 5 seconds.."
Start-Sleep -Seconds 5
$buildInfo = ( Invoke-RestMethod -Method Get -UseDefaultCredentials -Uri "https://tfs.com:8443/Organization/_apis/build/builds/${buildID}?api-version=6.1-preview.6" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} )
while($buildInfo.status -eq "inProgress" -or $buildInfo.status -eq "notStarted") # keep checking till build completed
{
Write-Output "Build is $($buildInfo.status)... Sleep for 5 seconds.."
Start-Sleep -Seconds 5 # Start sleep for 5 seconds
$buildInfo = ( Invoke-RestMethod -Method Get -UseDefaultCredentials -Uri "https://tfs.com:8443/Organization/_apis/build/builds/${buildID}?api-version=6.1-preview.6" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} )
}
Write-Output "Build Status : $($buildInfo.status)" # print build status
Write-Output "Build Result : $($buildInfo.result)" # print build result
displayName: 'Trigger Another Build Pipeline'
You can install az devops extension in your pipeline agent and then you can call az pipeline CLI commands" to manage other build or release pipelines. Next, you can call az pipeline CLI commands from your main pipeline and for this you can use AzureCLI task or Bash task.
Here is an implementation I use with the following:
PowerShell CmdLet "Invoke-RestMethod" https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod?view=powershell-5.1
Azure DevOps REST API https://learn.microsoft.com/en-us/rest/api/azure/devops/build/builds/queue?view=azure-devops-rest-5.0
The $(System.AccessToken) variable from DevOps. This has the
advantage that a PAT is not necessary. More Infomation at https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#systemaccesstoken
Unfortunately the target pipeline is not in another project
I created a stage
######### stage_call_other_pipelines ###################
#########################################################
- stage: stage_call_other_pipelines
displayName: "call other pipelines"
jobs:
#XYZ deployment
- job: job_call_XYZ_deployment
displayName: "execute XYZ deployment"
steps:
- checkout: none
- task: PowerShell#2
displayName: "via REST API"
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
targetType: 'inline'
script: |
#url
$url = 'https://dev.azure.com/XYZOrganization/XYZProject/_apis/build/builds?api-version=5.0'
#header
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/json")
$headers.Add("Authorization","Bearer $env:SYSTEM_ACCESSTOKEN");
#body
$body = " {
`n `"definition`": {
`n `"id`": 134
`n },
`n `"templateParameters`": {
`n `"ParameterA`": `"ParameterValueA`",
`n `"ParameterB`": `"ParameterValueB`"
`n }
`n }"
#call rest api
$response = Invoke-RestMethod $url -Method 'POST' -Headers $headers -Body $body
#output
$response | ConvertTo-Json
failOnStderr: true
pwsh: true
The token is passed to the agent as an environment variable. The pipeline and its parameters are defined in the body.
Additional
On the pipeline that is to be executed, permissions must be adjusted.
Go to the desired pipeline, click in the right upper corner on the
menu button and select "Manage security"
A form will apear. Choose the Build Service service principal and set "Queue builds" on "Allow"
Since the OP didn't specify how they wanted to accomplish this, I'll share how I'm doing this without YAML pipelines. I use the Edit pipeline option then the meatball menu to select triggers and I can specify which triggering build with branch filters.

Execute Mutliple release pipeline

I have 50+ release pipelines for the production environment and each time doing production has to manually approve each of these pipelines for deployment.
Is there any way to automate and in a single click, all the pipeline gets approved and gets deployed?
The only way I am aware of accomplishing this would be to combine the 50+ pipelines into one master release and have those dependent on a separate stage who is deploying to a gated environment. Assign an approval on that environment. Thus once that stage in the "approval environment" is approved all subsequent stages will run.
In Azure DevOps, we can configure approvals, It checks all the resources used in that stage, such as source code and follow the target server deployed, this should let managers to check and agree this deploy, batch approval is meaningless.
If you insist on approving multiple release pipeline stages at the same time. we recommend that you can remove these approvals.
Or we can use API to list all Approvals and get the approval id, then update status of an approval.
In addition, if there are multiple approvals in the pipeline, it will only approve the current stage.
Steps:
Create a PAT token->create a build pipeline->click the tab variable and add the variable pat = {Token}, then set it to secret variable->add task powershell and enter the following script to approve.
$connectionToken="$(pat)"
$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($connectionToken)"))
$PipelineUrl = "https://vsrm.dev.azure.com/{Org name}/{Project name}/_apis/release/approvals?api-version=5.1"
$Pipelines = (Invoke-RestMethod -Uri $PipelineUrl -Method Get -UseDefaultCredential -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)})
$ ApprovalId = $Pipelines.value.id
Write-Host $ ApprovalId
# List all Approvals and get approval id
ForEach ($Id in $ ApprovalId)
{
$baseUrl = "https://vsrm.dev.azure.com/{Org name}/{Project name}/_apis/release/approvals/$($Id)?api-version=5.1"
$body ="{
`"status`": `"approved`"
}"
$response = Invoke-RestMethod -Uri $baseUrl -ContentType "application/json" -Body $body -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method PATCH
}

Open a bug on release fails in VSTS Continuous Deployment

I have configured and scheduled only release definition on VSTS for Azure solution deployment which is having PowerShell tasks. But I want to open a bug or work item in VSTS if release/ deployment fails.
Is it possible in VSTS.
Yes, it’s possible for VSTS to create a bug if a release failed.
Add another PowerShell task in the end of your release definition to create a bug if the previous task fails. Detail settings for the PowerShell task as below:
Select Only when a previous task has failed for Rume this task option, so if the previous task failed, this PowerShell task will be executed.
The add the powershell script to create a bug work item like:
$witType="Bug"
$witTitle="title"
$u="https://account.visualstudio.com/DefaultCollection/project/_apis/wit/workitems/`$$($witType)?api-version=1.0"
$body="[
{
`"op`": `"add`",
`"path`": `"/fields/System.Title`",
`"value`": `"$($witTitle)`"
}
]"
$user = "username"
$token = "PAT"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$result=Invoke-RestMethod -Method PATCH -Uri $u -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -ContentType "application/json-patch+json" -Body $body