Azure Pipelines 無法刪除 Build Pipeline 的處理方法

有時候我們想要在 Azure DevOps Services 上面刪除一些早期的 Pipeline (Build),但是刪除時卻會遇到 One or more builds associated with the requested pipeline(s) are retained by a release. The pipeline(s) and builds will not be deleted. 的問題,導致我們怎樣都無法刪除。這篇文章我打算來說明一下幾個重要觀念,以及如何才能成功刪除這些用不到的 Pipelines。

發生錯誤的過程

  1. 刪除 Pipeline (Build)

    刪除 Pipeline

  2. 刪除時出現以下錯誤訊息

    One or more builds associated with the requested pipeline(s) are retained by a release. The pipeline(s) and builds will not be deleted.

    One or more builds associated with the requested pipeline(s) are retained by a release. The pipeline(s) and builds will not be deleted.

我簡單翻譯一下這段錯誤訊息:

  • One or more builds associated with the requested pipeline(s) are retained by a release.

    你所要求刪除的 pipeline(s) 的一個或多個 builds (建置) 被一個 release (發行) 所保留(retained)。

  • The pipeline(s) and builds will not be deleted.

    這些 pipeline(s) 以及 builds 將不會被刪除。

我們在 Azure DevOps Services 之中的重要觀念如下:

  1. 一個 pipeline 會包含一個或多個 builds (有時候 builds 也會說成 runs)

  2. 一個 release 可能會從 pipeline 取得 builds 來發行

  3. 所以 release 跟 pipeline 產生的 build 之間會有一個或多個 leases(租約) 關係

  4. 在 release 之中可以對 builds 設定 Retain (保留) 的條件

    Release - Retention

  5. 早期 release 的設定預設會永久保留所有的 builds,但現在的 release 最多只會保留 365 天,而且在專案設定中有明確的預設值設定!

    Project Settings - Retention policy settings

簡單來說,就是你只要有設定 release (CD),我們通常都會關聯一個 Pipelines (CI) 的 build (通常是最新版的 build artifact),所以這些 builds 或 runs 會預設保留下來一段時間,保留多久是由一條一條的 lease 決定的。重點是,只要這些 builds 被保留住,你就無法刪除 pipeline!

如何才能順利刪除 Pipeline

事實上,就算你手動刪除 Release 也是無法解鎖這些 Builds 的,因為刪除 Release 之後會被放到「資源回收匣」達 30 天,所以關聯的 Builds 依然無法被刪除。

Deleted pipelines

所以重點就是如何讓這些 Builds 不要被保留(Retain),方法如下:

  1. 進入特定 Pipeline 並顯示所有 Builds,對每一個 Builds 選擇 View retention leases (檢視保留租約) 設定

    View retention leases

  2. 你可以在 Run retention 對話框看到這個 Build 的保留原因,並且可以按下 Remove all 刪除所有保留設定

    Run retention

  3. 當你把所有 Builds 的 Leases 都移除,就可以順利刪除 Pipeline 了!

    是的,就是要一筆一筆刪除,有點麻煩!

批次刪除 Leases 的方法

如果要批次刪除,只能透過 Leases - REST API (Azure DevOps Build) 來刪除了。

我已經寫好一份了,超好用:

  • 刪除特定專案下所有 Pipelines (Build Definition) 底下所有的 builds 底下所有的 leases

    $personalAccessToken = "[PAT]"
    
    $organization = "[OrgId]"
    $project = "[ProjectId]"
    
    $apiVersion = "6.0"
    
    $token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($personalAccessToken)"))
    $header = @{authorization = "Basic $token"}
    
    function DeleteLease($definitionId) {
        $url = "https://dev.azure.com/$organization/$project/_apis/build/retention/leases?api-version=$($apiVersion)&definitionId=$definitionId"
        $leases = (Invoke-RestMethod -Method GET -Uri $url -ContentType "application/json" -Headers $header )
    
        foreach ($lease in $leases.value) {
            $leaseId = $lease.leaseId
            $url = "https://dev.azure.com/$organization/$project/_apis/build/retention/leases?ids=$($leaseId)&api-version=$($apiVersion)"
            $ignore = Invoke-RestMethod -Method DELETE -Uri $url -ContentType "application/json" -Headers $header
            Write-Host "DELETE $url"
        }
    }
    
    $url = "https://dev.azure.com/$organization/$project/_apis/build/definitions?api-version=$apiVersion"
    Write-Host $url
    
    $buildDefinitions = Invoke-RestMethod -Uri $url -Method Get -ContentType "application/json" -Headers $header
    
    foreach ($def in $builddefinitions.value) {
        Write-Host $def.id $def.queueStatus $def.name
        DeleteLease $def.id
    }
    
  • 刪除特定專案下特定一個 Pipelines (Build Definition) 底下所有 builds 的 leases

    $personalAccessToken = "[PAT]"
    
    $organization = "[OrgId]"
    $project = "[ProjectId]"
    $definitionId = "[BuildDefinitionId]"
    
    $apiVersion = "6.0"
    
    $token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($personalAccessToken)"))
    $header = @{authorization = "Basic $token"}
    
    function DeleteLease($definitionId) {
        $url = "https://dev.azure.com/$organization/$project/_apis/build/retention/leases?api-version=$($apiVersion)&definitionId=$definitionId"
        $leases = (Invoke-RestMethod -Method GET -Uri $url -ContentType "application/json" -Headers $header )
    
        foreach ($lease in $leases.value) {
            $leaseId = $lease.leaseId
            $url = "https://dev.azure.com/$organization/$project/_apis/build/retention/leases?ids=$($leaseId)&api-version=$($apiVersion)"
            $ignore = Invoke-RestMethod -Method DELETE -Uri $url -ContentType "application/json" -Headers $header
            Write-Host "DELETE $url"
        }
    }
    
    $url = "https://dev.azure.com/$organization/$project/_apis/build/definitions?api-version=$apiVersion"
    Write-Host $url
    
    $buildDefinitions = Invoke-RestMethod -Uri $url -Method Get -ContentType "application/json" -Headers $header
    
    DeleteLease $definitionId
    

    你可以從網址列上取得 $definitionId,如下圖示:

    definitionId

相關連結

留言評論