How can you programmatically raise a conditional PR? - azure-devops

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

Related

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

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
}

Get the branch name from tag using Azure DevOps api

Can someone help me to get the actual branch name if I have the tag associate with it?
Azure DevOps API would be best in my case. Thanks in advance.
SO far I get the API that gives all the tags for particularly repo but this does not solve my issue.
https://dev.azure.com/{{organization}}/{{project}}/_apis/git/repositories/{{repository_id}}/refs?filter=tags/&api-version=6.0
But it does not give me any branch information associated with it.
There isn’t such REST API to get branch name by a tag or commit. In the Azure DevOps, Tags are created based on the commit id, and a commit or tag can belong more than one branches. Let’s illustrate by below graph:
A---B---E---F master
\ /
C---D dev
This is a branch structure with master and dev branch. And dev branch merge into master with commit F. If you want to get the branch name based on commit D, you will get two branch names. Commit D belongs to two branch: dev (first parent) and master (second parent).
We can get the branch name via condition and multiple REST APIs, also we could get the branch name via git cmd.
a. List all tags and get the objectId.
GET https://dev.azure.com/{Org name}/{Project name}/_apis/git/repositories/{repo ID}/refs?filter=tags/&api-version=6.0
b. Get the tag info and commit ID via objectId
Note: the field taggedObject.objectId is commit ID
GET https://dev.azure.com/{organization}/{project}/_apis/git/repositories/{repositoryId}/annotatedtags/{objectId}?api-version=6.0-preview.1
Workaround 1
Now we could get the commit details and get the branch name(s) contain the given commit, we can use git command:
git branch --contains <commit>
Workaround 2
We could list all commits info on a branch via this API
GET https://dev.azure.com/{Org name}/_apis/git/repositories/{repositoryId}/commits?searchCriteria.itemVersion.version={branch name}&api-version=6.0
And now, we get commit id which contain the tag and all commit ID on a branch, add if{} or eq() to check it, if it return True, we could know this branch contain the tag
Update1
My test repo contains branch main and test01, the branch main contains the tag Tag01, the branch test01 contains the tag Tag01 and tag02
Power shell script:
$ListAllTagsURL="https://dev.azure.com/{Org name}/{Project name}/_apis/git/repositories/{Repo ID}/refs?filter=tags/&api-version=6.0"
$PAT="{PAT}"
$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($PAT)"))
$ListAllTags = Invoke-RestMethod -Uri $ListAllTagsURL -Headers #{Authorization = "Basic {0}" -f $base64AuthInfo} -Method get
#List all tags name
#write-host $ListAllTags.value.name
foreach($Tag in $ListAllTags.value){
if($Tag.name -eq "{tag name such as refs/tags/Tag01}"){
$objectId = $Tag.objectId
$TagName = $Tag.name
}
}
#Get tag details and commit ID
$TagURL="https://dev.azure.com/{Org name}/{Project name}/_apis/git/repositories/{Repo ID}/annotatedtags/$($objectId)?api-version=6.0-preview.1"
$TagInfo = Invoke-RestMethod -Uri $TagURL -Headers #{Authorization = "Basic {0}" -f $base64AuthInfo} -Method get
$TagCommitID = $TagInfo.taggedObject.objectId
#write-host $TagCommitID
#List all branch
$url="https://dev.azure.com/{Org name}/{Project name}/_apis/git/repositories/{Repo ID}/refs?filter=heads&api-version=6.1-preview.1"
$BranchInfo = Invoke-RestMethod -Uri $url -Headers #{Authorization = "Basic {0}" -f $base64AuthInfo} -Method get
foreach($Branch in $BranchInfo.value){
#write-host $Branch.name
$BranchName = $Branch.name.split("/",3)[-1]
#write-host $BranchName
#List all commit ID via Branch Name
$BranchDetailUrl="https://dev.azure.com/v-viliu/_apis/git/repositories/{Repo ID}/commits?searchCriteria.itemVersion.version=$($BranchName)&api-version=6.0"
$BranchDetailInfo = Invoke-RestMethod -Uri $BranchDetailUrl -Headers #{Authorization = "Basic {0}" -f $base64AuthInfo} -Method get
#write-host $BranchDetailInfo.value.commitId
foreach($CommitID in $BranchDetailInfo.value.commitId){
If($CommitID -eq $TagCommitID){
write-host $BranchName "Contain this tag" $TagName
}
}
}
Result:

Managing branches with Azure DevOps Classic Build Pipelines and TFVC

How do you manage building from branches when using Azure DevOps Classic Build Pipelines and TFVC?
I believe that the only viable option is to copy the build pipeline with a new name and update the source code mapping to point to the new TFVC branch.
I see the ADO web UI provides the option to clone an individual build definition, yet as I have over 200+ build pipelines to clone whenever I branch is there a more efficient way to do this? Or is the only option to write a custom tool to leverage the ADO REST Api?
Since you need to clone pipelines in batches, using scripts to run the Rest API will be a reasonable method. As far as I know, there is no easy way out of the box other than this.
You could try the following PowerShell Script Sample:
$DefinitionIds = "PipelineIDs" #InPut All Pipelineids(e.g. "324,323,xxx" )
$DefinitionId = $DefinitionIds.split(",");
$token = "PAT Token"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($token)"))
foreach ($i in $DefinitionId)
{
echo $i
$url="https://dev.azure.com/{OrganizationName}/{ProjectName}/_apis/build/definitions/$($i)?api-version=6.0"
$response = Invoke-RestMethod -Uri $url -Headers #{Authorization = "Basic $token"} -Method Get -ContentType application/json
Write-Host "$($response | ConvertTo-Json -Depth 100)"
$response.repository.properties.tfvcMapping= '{"mappings":[{"serverPath":"$/TFVCBranchName","mappingType":"map","localPath":"\\"}]}' # ServerPath is the Branch name
$response.repository.name = "TFVCRepoName" #Repo Source Name
$response.name = "Pipeline $i Clone" # Cloned PipelineName
echo $response.name
$url1= "https://dev.azure.com/{OrganizationName}/{ProjectName}/_apis/build/definitions?api-version=6.0"
$json = #($response) | ConvertTo-Json -Depth 100
$response1 = Invoke-RestMethod -Uri $url1 -Headers #{Authorization = "Basic $token"} -Method Post -Body $json -ContentType application/json
}
Here are the Two Rest APIs used in the Script:
Definitions - Get
Definitions - Create
Result:
The cloned Pipeline will be set to the new TFVC branch and Build definition name.

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
}

How to get Releases Pipelines pre-deployment gates status through REST API in Azure DevOps

I am using Azure DevOps (ADO) REST APIs to build dashboards on Power BI. I wanted to query whether the releases pipelines of all projects in my organization have pre-deployment gates of "SonarCloud Quality Gate status" by using the REST APIs invocation.
I have tried this API (Releases - List) GET https://vsrm.dev.azure.com/{organization}/{project}/_apis/release/releases?api-version=5.1. I am reading the MS docs and thought this particular GateStatuswill be useful for my needs, but this GateStatus does not show up when I am testing on my pipelines that have pre-deployment gates of "SonarCloud Quality Gate status" configured.
I found this API (Release - Get Release) GET https://vsrm.dev.azure.com/{organization}/{project}/_apis/release/releases/{releaseId}?api-version=5.1 with MS docs. That preDeploymentGates shows the information I want when I am testing in POSTMAN. But, the problem is it requires releaseId in each query which is troublesome for me since my ultimate goal is to have a list of ALL releases in multiple projects of multiple organizations.
Thanks.
since my ultimate goal is to have a list of ALL releases in multiple projects of multiple organizations.
Since you want to get the Pipelines pre-deployment gates status for ALL releases in multiple projects of multiple organizations, so we use the API (Release - Get Release) to get the gates status, then loop this API in the all Release pipelines in one project, next, loop the all projects in one ORG, latest, loop all ORGs.
First, We need to create a All accessible organization PAT:
Because we need access multiple organizations.
Second, set a array to store the multiple organization names:
$ORGNames=#("Organization1","Organization2")
Then using following powershell scripts to loop the API (Release - Get Release):
$connectionToken="PAT"
$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($connectionToken)"))
$ORGNames=#("Organization1","Organization2")
ForEach ($ORGName in $ORGNames[0,1])
{
$ProjectUrl = "https://dev.azure.com/$($ORGName)/_apis/projects?api-version=5.1"
Write-Host "URL: $ProjectUrl"
$Projects = (Invoke-RestMethod -Uri $ProjectUrl -Method Get -UseDefaultCredential -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)})
$ProjectId = $projects.value.id
Write-Host "Projects = $ProjectId"
ForEach ($Pt in $ProjectId)
{
$baseUrl = "https://vsrm.dev.azure.com/$($ORGName)/$($Pt)/_apis/release/releases?api-version=5.1"
$ReleaseId = (Invoke-RestMethod -Uri $baseUrl -Method Get -UseDefaultCredential -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)})
$ReleaseIds = $ReleaseId.value.id
Write-Host "ReleaseID = $ReleaseId"
foreach($ReleaseID in $ReleaseIds){
$url = "https://vsrm.dev.azure.com/$($ORGName)/$($Pt)/_apis/release/releases/$($ReleaseID)?api-version=5.1"
$GatesStatus = (Invoke-RestMethod -Uri $url -Method Get -UseDefaultCredential -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)})
Write-Host "GatesStatus For ORGName $ORGName Project $Pt And Release ID $ReleaseID= $($GatesStatus.environments.deploySteps.preDeploymentGates.status | ConvertTo-Json -Depth 100)"
}
}
}
The test result:
Hope this helps.