如何批次取消 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 的最大優點,就是可以預先設定好 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 的重要名詞與其關聯:

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
    • task 與 script 是一個 Pipeline 中的最小單位 (smallest building block)
    • task 是一個預先封裝好的腳本,方便你可以快速的完成某些複雜的工作
    • task 做不到的事情,都可以透過自定義的 script 來完成
    • script 在 Windows Agent 預設是 Command Prompt
    • script 在 Linux Agent 預設是 Bash
  • 每個 artifact 是一堆檔案的集合,或是由某一次 run 發行的封裝檔

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

微軟在各種產品與 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 來重新執行

相關連結

留言評論