如何批次取消 Azure Pipelines 中所有排隊中的 Runs 或 Builds | The Will Will Web

The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何批次取消 Azure Pipelines 中所有排隊中的 Runs 或 Builds

昨天我建立了一個有 18 個 Repos 的專案,在批次設定的過程,我肯定要用 Azure Pipelines 的 YAML syntax 來撰寫,否則用傳統編輯器設定,肯定按到手酸。不過,在撰寫的過程中,有一次不小心寫壞了,導致一次 18 個 CI 同時啟動,但我只有四條 Pipelines 可用,一想到要等全部跑完才能測試下一輪,就覺得我應該要寫支小程式批次取消。因此這篇文章就是為此而生,讓你快速的取消所有尚未啟動的 Pipelines 作業。

要透過 Azure DevOps REST APIs 取消尚未啟動的建置作業(Builds),基本上就兩個步驟:

  1. 取得尚未啟動(notStarted)的建置作業清單

    Authorization: Basic BASE64ENCODE(':' + PAT)
    
    GET https://dev.azure.com/{organization}/{project}/_apis/build/builds?statusFilter=notStarted&api-version=6.0
    

    BuildStatus

  2. 更新建置作業內容

    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 則有兩種方法:

  1. 使用 Invoke-RestMethod 呼叫 APIs
  2. 使用 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 的最大優點,就是可以預先設定好 OrganizationNameProjectName

# 僅設定好預設的組織 (我通常都會這樣設定)
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 的重要名詞與其關聯:

Key concepts for new Azure Pipelines users

  • 所謂 trigger 是用來告訴一個 pipeline 開始 run (執行)
  • 一個 pipelines 可能包含一個或多個 stages (階段)
  • 一組 pipelines 可以一次部署到多個 environments (環境)
  • 一組 pipelines 可以包含一個或多個 stage
  • 一個 stage 可以包含一個或多個 jobs
  • 一個 jobs 可以包含一個或多個 steps (執行步驟)
    • jobs 可以跑在一個 agent
    • jobs 可以跑在一個 agentless 的環境上 (有些 jobs 是不需要 agent 就能執行)
  • 一個 step 可以是一個 task 或是一個 script
    • taskscript 是一個 Pipeline 中的最小單位 (smallest building block)
    • task 是一個預先封裝好的腳本,方便你可以快速的完成某些複雜的工作
    • task 做不到的事情,都可以透過自定義的 script 來完成
    • script 在 Windows Agent 預設是 Command Prompt
    • script 在 Linux Agent 預設是 Bash
  • 每個 artifact 是一堆檔案的集合,或是由某一次 run 發行的封裝檔

很複雜對不對?這已經是精鍊過的版本了,以前更亂! 😆

微軟在各種產品與 API 的「命名」上一直有個 "好" 習慣,只要產品發展一段時間,只要覺得產品在演進的過程覺得當初名字取的不太好,就會直接改掉,沒在跟你五四三的。例如:以前 Pipelines 被稱之為 BuildsBuild pipelines,而早期的 BuildsTests 則被調整為 Runs 這個字。我覺得這樣改名也沒甚麼錯,這對「新人」比較有好處,因為新的名字可以比較好的建構更清楚的概念模型(Mindset)。不過唯一的缺點,就是對我們這些「老人」不太友善,一天到晚改名,改到懷疑人生耶。

上述提到的 run 就是每次執行的紀錄,這是一個相對較新的名詞,早期這個名詞叫做 buildtest

所以我們常會在官方文件Azure DevOps Services REST API ReferenceAzure 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 來重新執行

相關連結