昨天我建立了一個有 18 個 Repos 的專案,在批次設定的過程,我肯定要用 Azure Pipelines 的 YAML syntax 來撰寫,否則用傳統編輯器設定,肯定按到手酸。不過,在撰寫的過程中,有一次不小心寫壞了,導致一次 18 個 CI 同時啟動,但我只有四條 Pipelines 可用,一想到要等全部跑完才能測試下一輪,就覺得我應該要寫支小程式批次取消。因此這篇文章就是為此而生,讓你快速的取消所有尚未啟動的 Pipelines 作業。
要透過 Azure DevOps REST APIs 取消尚未啟動的建置作業(Builds),基本上就兩個步驟:
-
取得尚未啟動(notStarted)的建置作業清單
Authorization: Basic BASE64ENCODE(':' + PAT)
GET https://dev.azure.com/{organization}/{project}/_apis/build/builds?statusFilter=notStarted&api-version=6.0
BuildStatus
-
更新建置作業內容
Authorization: Basic BASE64ENCODE(':' + PAT)
PATCH https://dev.azure.com/{organization}/{project}/_apis/build/builds/{buildId}?api-version=6.0
{"status": "Cancelling"}
BuildStatus
而要透過 PowerShell 來呼叫 Azure DevOps REST APIs 則有兩種方法:
- 使用
Invoke-RestMethod
呼叫 APIs
- 使用
az devops invoke
呼叫 APIs
使用 Invoke-RestMethod
呼叫 APIs
使用 Invoke-RestMethod
的最大優點,就是「速度快」,沒有什麼繁瑣的檢查程序,拿到 PAT 之後就是直接發出 API 要求,快、狠、準。
我通常會把常用的 PAT 儲存在名為 AZURE_DEVOPS_EXT_PAT
的環境變數中,你也可以透過以下命令設定目前 Session 下的環境變數:
$env:AZURE_DEVOPS_EXT_PAT = "{{PAT}}"
剩下的直接看程式碼說故事:
function Cancel-AzurePipelineBuilds {
param (
$OrganizationName,
$ProjectName,
$Status
)
$AzureDevOpsAuthHeader = @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($env:AZURE_DEVOPS_EXT_PAT)")) }
$Uri= "https://dev.azure.com/$($OrganizationName)/$($ProjectName)/_apis/build/builds?statusFilter=$($Status)&api-version=6.0"
$Builds = Invoke-RestMethod -Uri $Uri -Headers $AzureDevOpsAuthHeader -Method get -ContentType "application/json"
$BuildsToCancel = $Builds.value
ForEach($build in $BuildsToCancel)
{
$build.status = "Cancelling"
$body = $build | ConvertTo-Json -Depth 10
$urlToCancel = "https://dev.azure.com/$($OrganizationName)/$($ProjectName)/_apis/build/builds/$($build.id)?api-version=6.0"
Invoke-RestMethod -Uri $urlToCancel -Method Patch -ContentType application/json -Body $body -Header $AzureDevOpsAuthHeader | Out-Null
Write-Output "BuildId $($build.id): Canceled ($($build.definition.name))"
}
}
$OrganizationName = "{{OrgName}}"
$ProjectName = "{{ProjectName}}"
Cancel-AzurePipelineBuilds $OrganizationName $ProjectName 'notStarted'
Cancel-AzurePipelineBuilds $OrganizationName $ProjectName 'inProgress'
使用 az devops invoke
呼叫 APIs
使用 az devops invoke
的最大優點,就是可以預先設定好 OrganizationName
與 ProjectName
# 僅設定好預設的組織 (我通常都會這樣設定)
az devops configure --defaults organization=https://dev.azure.com/willh
# 設定好預設的組織與專案
az devops configure --defaults organization=https://dev.azure.com/willh project=AngularTW
然後最重要的,你只要準備好 Azure DevOps 的 PAT (Personal Access Token) 放到名為 AZURE_DEVOPS_EXT_PAT
的環境變數,就不需要再執行 az devops login
登入!
$env:AZURE_DEVOPS_EXT_PAT = "{{PAT}}"
剩下的直接看程式碼說故事:
function Cancel-AzurePipelineBuilds {
param (
$ProjectName,
$Status
)
$runs = $(az pipelines runs list -p $($ProjectName) --status $($Status) -o json | ConvertFrom-Json)
$PatchFile = New-TemporaryFile
echo '{"status": "Cancelling"}' | Out-File $PatchFile -Encoding utf8
ForEach($run in $runs)
{
az devops invoke --area build --resource builds --route-parameters buildId=$($run.id) project=$($ProjectName) --in-file $PatchFile --http-method patch | Out-Null
Write-Output "BuildId $($run.id): Canceled ($($run.definition.name))"
}
}
$ProjectName = "{{ProjectName}}"
Cancel-AzurePipelineBuilds $ProjectName 'notStarted'
Cancel-AzurePipelineBuilds $ProjectName 'inProgress'
其他補充的背景知識
這裡我說明一下目前我所知道關於 Azure Pipelines 的重要名詞與其關聯:

很複雜對不對?這已經是精鍊過的版本了,以前更亂! 😆
微軟在各種產品與 API 的「命名」上一直有個 "好" 習慣,只要產品發展一段時間,只要覺得產品在演進的過程覺得當初名字取的不太好,就會直接改掉,沒在跟你五四三的。例如:以前 Pipelines
被稱之為 Builds
或 Build pipelines
,而早期的 Builds
或 Tests
則被調整為 Runs
這個字。我覺得這樣改名也沒甚麼錯,這對「新人」比較有好處,因為新的名字可以比較好的建構更清楚的概念模型(Mindset)。不過唯一的缺點,就是對我們這些「老人」不太友善,一天到晚改名,改到懷疑人生耶。
上述提到的 run 就是每次執行的紀錄,這是一個相對較新的名詞,早期這個名詞叫做 build
或 test
!
所以我們常會在官方文件、Azure DevOps Services REST API Reference 或 Azure DevOps CLI 看到一些似是而非的名詞,真的讓人非常困惑。
所謂的 Runs
是一個抽象概念,我們在 Azure DevOps CLI 會看到一個 az pipelines runs 命令,他其實就是在講 Builds
的意思。
而我們在 Azure DevOps Services REST API Reference 看到的 Build / Builds 其實就是一樣的東西,他就是 Runs
的意思。
基本上,產品名稱要怎麼改都可以,但要是你改了 API 的端點,就會有一堆人跟你拼命!😅
好吧,我再來補充一些官方文件沒有寫的很清楚的關聯資訊:
- 一個 pipeline 包含了一個 definition
- 想像 pipeline 是一棟蓋好的房子
- 然而 definition 就是這棟房子的藍圖
- 我們經常有修繕房子的需求,每次都會需要調整藍圖後才施工
- 我們經常會調整 pipeline 的需求,每次都會調整 definition 之後才會跑 (run)
- 因為 definition 有不斷變更的議題,所以 Azure DevOps 預設會將你每次修改進行版控
- 一個 run 就是一個 build,而每個 build/run 包含了一組當下的 build definition
- 你在 build 失敗的時候按下 re-run 按鈕,其實是把之前的 build definition 重跑一次而已,並不會去 Pipeline 抓取最新的 definition 來重新執行
相關連結