﻿# 如何從 Azure DevOps Service 刪除自動建立的 Service connections

在我的 Azure DevOps Service 上面有個專案，之前已經都設定好 CI/CD 並運作正常一段時間，最近因為 Azure Subscription 有些異動，導致原本的 Azure Resource Manager **服務連線** (Service connection) 無法正常運作，不但無法修改，也刪除不掉。我手動建立一條新的服務連線後，原本的 Release 已經可以正常部署，但是原先透過 Azure Pipelines 自動建立的**服務連線**卻怎樣都刪除不了。最後向微軟提出技術支援要求之後，才得知一個透過 REST API 刪除的方法，透過 Web 介面已經確定無法刪除該項目。

### 認識 Azure DevOps Services REST API

其實整個 Azure DevOps Services 複雜網頁介面的背後，都是一支一支的 Web API 建構而成，在 Web UI 尚未更新之前，其實 API 會走在最前面，所以有些功能確實無法透過 Web UI 進行新增、修改或刪除。要上手 Azure DevOps Services REST API 的第一步，就是查看 [Azure DevOps Services REST API Reference](https://learn.microsoft.com/en-us/rest/api/azure/devops/) 文件！

要成功透過 REST API 操作 Azure DevOps Services 服務，底下 2 件事要先搞定：

1.  認識 REST API 的封包結構(Request/Response)
    
    -   要求網址 (Request URI)
        
        若以 **Azure DevOps Service** 來說，可以用以下網址結構：
        
        ```
        VERB https://dev.azure.com/{organization}[/{team-project}]/_apis[/{area}]/{resource}?api-version={version}
        ```
        
        若是 **Azure DevOps Server** 的話，可以用以下網址結構：
        
        ```
        VERB https://{instance}[/{team-project}]/_apis[/{area}]/{resource}?api-version={version}
        ```
        
    -   要求標頭 (Request message header)
        
        這裡最重要的應該是 `Authorization` 標頭，用來對操作者的身分進行驗證！
        
        ```
        Authorization: Basic BASE64PATSTRING
        ```
        
        > 這裡的 `BASE64PATSTRING` 為 `:PAT` 的 Base64 編碼字串，也就是 **冒號(:)** + **PAT字串**。
        
        > PAT = Personal Access Token
        
    -   要求內文 (Request message body)
        
        通常要求內文的 `Content-Type` 都以 `application/json` 為主。
        
    -   回應標頭 (Response message header)
        
        回應標頭最重要的不外乎是 `狀態碼` 與內文的 `Content-Type` ( `application/json` )。
        
    -   回應內文 (Response message body)
        
        通常回應內文內容類型也以 `application/json` 為主，但也支援 XML 類型 (`text/xml`)。
        
2.  學習如何發出 REST API 要求
    
    -   認證方式
        
        你可以使用 **Basic Auth** 或 **OAuth 2.0** 的方式與 Azure DevOps Services REST API 進行驗證。
        
    -   使用 [cURL](https://curl.haxx.se/) 工具發出要求
        
        我以取得 Service connections 的 Endpoints 為例，命令與參數大概長這樣：
        
        ```bash
        curl -s -u :{PAT} https://dev.azure.com/{organization}/{team-project}/_apis/serviceendpoint/endpoints?api-version=5.1-preview.2
        ```
        
        > 建立 PAT 請參考 [Authenticate access with personal access tokens](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=current-page) 這份文件。
        
    -   使用 [Postman](https://www.getpostman.com/) 工具發出要求
        
        這裡最重要的就是選對 **Auth** 的方式，請參考以下畫面進行設定即可：
        
        ![2020-02-10\_21-23-07](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/43759d794f0a4ce7cc22-74153479-adc74f80-4c4b-11ea-9de8-c8587b9a3b85.jpg)
        

### 取得並刪除自動建立的 Service Endpoints

> 注意：你從 Web UI 看到的 Service Connections 在 REST API 中的名稱為 [Service Endpoints](https://learn.microsoft.com/en-us/rest/api/azure/devops/serviceendpoint/endpoints/get%20service%20endpoints)

1.  取得 Service Endpoints 清單
    
    你可以透過以下 API 要求取得所有 Service Endpoints
    
    ```bash
    curl -s -u :{PAT} https://dev.azure.com/{organization}/{team-project}/_apis/serviceendpoint/endpoints?api-version=5.1-preview.2 | jq ".value[]"
    ```
    
    我拿這份資料跟 Web UI 上面的 Service connections 顯示的項目比較一下，發現數量不一致！我在 Azure DevOps Service 上面專案內的 Service connections 其實有 4 筆資料，但是透過 REST API 卻只看到 3 筆而已！
    
    經過研究後發現，原來已經認證失敗的 Service connections 不會自動出現在 REST API 中，如果要顯示所有的 Service Endpoints 還必須加上 `includeFailed=true` 這個 URI 參數才行，例如：
    
    ```bash
    curl -s -u :{PAT} https://dev.azure.com/{organization}/{team-project}/_apis/serviceendpoint/endpoints?api-version=5.1-preview.2&includeFailed=true | jq ".value[]"
    ```
    
    我用 jq 的過濾語法，僅列出**無法連線**的清單：
    
    ```bash
    curl -s -u :{PAT} "https://dev.azure.com/{organization}/{team-project}/_apis/serviceendpoint/endpoints?api-version=5.1-preview.2&includeFailed=true" | jq "[.value[] | select(.isReady==false)]"
    ```
    
2.  先設法取得無法刪除的 Endpoint Id
    
    我用 jq 的過濾語法，僅列出**自動建立**的 Ids：
    
    ```bash
    curl -s -u :{PAT} "https://dev.azure.com/{organization}/{team-project}/_apis/serviceendpoint/endpoints?api-version=5.1-preview.2&includeFailed=true" | jq '[(.value[] | select(.data.creationMode=="Automatic")).id]'
    ```
    
    或是直接用 Web UI 進行查找：
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/0e3a5557c07e763d80a6-74153995-b2d8ce80-4c4c-11ea-9b9a-68f7245e351a.jpg)
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/459226ac7a0522d2a4ce-74153997-b53b2880-4c4c-11ea-9d27-7f369bdb38e9.jpg)
    
3.  先取得該 Endpoint Id 的完整 JSON 內容
    
    將內容輸出成 `data.txt` 檔案
    
    ```bash
    curl -s -u :{PAT} "https://dev.azure.com/{organization}/{team-project}/_apis/serviceendpoint/endpoints/e3ed4887-1647-4954-a76e-24c5fb2d6d19?api-version=5.1-preview.2" | jq "." > data.txt
    ```
    
4.  將該筆 Endpoint 資料更新為手動建立 (Manual)
    
    編輯 `data.txt` 檔案內容，做出以下修改：
    
    1.  將 `"creationMode"` 的值從 `"Automatic"` 改為 `"Manual"`
    2.  移除 `"spnObjectId"`
    3.  移除 `"appObjectId"`
    
    將內容透過 `PUT` 方法更新回去：
    
    ```bash
    curl -s -u :{PAT} -X PUT -H "Content-Type: application/json" -d "@data.txt" "https://dev.azure.com/{organization}/{team-project}/_apis/serviceendpoint/endpoints/e3ed4887-1647-4954-a76e-24c5fb2d6d19?api-version=5.1-preview.2" | jq "."
    ```
    
5.  刪除該筆 Endpoint 資料
    
    只要你將該筆 Endpoint 資料更新為手動建立模式，就可以從 Azure DevOps Service 的 Web UI 刪除了！
    
    或是直接透過 REST API 刪除該筆 Endpoint
    
    ```bash
    curl -s -u :{PAT} -X DELETE "https://dev.azure.com/{organization}/{team-project}/_apis/serviceendpoint/endpoints/e3ed4887-1647-4954-a76e-24c5fb2d6d19?api-version=5.1-preview.2" | jq "."
    ```
    

### 相關連結

-   [Azure DevOps Services REST API Reference](https://learn.microsoft.com/en-us/rest/api/azure/devops/)
-   [jq](https://stedolan.github.io/jq/)
