The Will Will Web

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

如何正確設定 Azure Pipelines 存取 Artifacts 的 npm 套件來源 (Feeds)

我昨天花了兩個小時,終於研究出怎樣才能正確設定 Azure Pipelines 存取 Azure Artifacts 的 npm 套件來源 (Feeds),設定的過程實在讓人心力交瘁,沒想到就這麼簡單的一個授權設定要花這麼久的時間才研究出正確的設定步驟。今天我就來說說如何在 Azure Pipelines 正確地設定存取 Azure Artifacts 上的 npm 套件來源 (Feeds)。

image

簡單來說,要對 Azure Pipelines 做出正確的設定,讓 npm install 可以成功存取放在 Azure Artifacts 上面的 npm 套件,就是沒有簡單的步驟!Period.

因為不同的存取情境,就有不同的設定流程,所以還真的不是很好釐清問題,也因此微軟的官方文件很難可以在一個地方找到完整的說明文件,這也是我為什麼花了兩個小時才解決權限設定的問題。

我辛苦的研究出,為了要能夠成功安裝執行 npm install 安裝套件,你大概會有 8 個維度來考量 npm registry 的認證與授權,所以設定的方法最多大概會有 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 = 512 種組合,複雜到一個只能靠北邊走!😅

  • 你的 Azure Artifacts 的 Feed 的範圍

    • 組織層級的 Feed (Organization-scoped Feed)
    • 專案層級的 Feed (Project-scoped Feed)
  • 你的 Azure Artifacts 的 Feed 是否有設定 Upstreams Sources (上游來源)

    • 有設定 Upstreams Sources
    • 沒有設定 Upstreams Sources
  • 你在 Azure Pipelines 的 Build Pipeline 使用的身分識別

    • 使用 Project-scope 身分識別
    • 使用 Collection-scope 身分識別
  • 你的 Azure Artifacts 的 Feeds 所在的組織是否跟 Build pipelines 位於同一個組織

    • 兩者為相同組織
    • 分別隸屬於不同組織
  • Azure Pipelines 的 Build pipeline 選用的 Task 是哪一個

  • 使用 npm task 來安裝套件時,是否使用 npm authenticate (for task runners) task 來提供認證

  • 你的 Node 專案使用 npm 套件的方法

    • 採用單一套件來源 (整個 npm install 過程只會存取一個 Registry)
    • 採用混合套件來源 (整個 npm install 過程只依據不同的 Scope 存取不同的 Registry)
  • 你的 npm registry 認證方式

    • 使用 Public source 抓取 npm 套件 (不用認證)
    • 使用 Private source 或 Custom registry 抓取 npm 套件 (需通過認證)
  • 透過 Service Connection 建立 npm 連線的認證方式

    • 使用 Username and Password
    • 使用 Authentication Token 或 Personal Access Token (PAT)

我實在沒辦法靠這一篇文章寫出 256 種使用情境的設定,所以我會大致說明一下每一項的注意事項,再加以融會貫通後,就能應付所有情境了。

你的 Azure Artifacts 的 Feed 的範圍

Azure Artifacts 的 Feed 可以設定兩種不同的使用範圍:

  1. 組織層級的 Feed (Organization-scoped Feed)

    這個 Feed 是給整個組織使用的,所以只要是組織內的任何專案都可以存取這個 Feed 上的 npm 套件。

  2. 專案層級的 Feed (Project-scoped Feed)

    這個 Feed 是給當前專案使用的,所以主要只會用在目前的專案上。

你的 Azure Artifacts 的 Feed 是否有設定 Upstreams Sources (上游來源)

Azure Artifacts 有個很厲害的功能,就是他可以設定 Upstreams Sources (上游來源),啟用之後,你的 Node 專案只需要設定一組 NPM Registry 就能存取所有不同來源的 npm 套件,例如公開的 public registry 的 npm 套件與 publish 到 Azure Artifacts Feed 的 npm 套件,而認證的過程也只需要設定一次,直接對 Azure Artifacts 的 Feed 做設定即可,因此設定上還算單純,不太會踩到什麼地雷。

透過 Upstreams Sources (上游來源) 有幾個極大的優點:

  1. 對於企業有嚴格管制網路的環境下,開發人員通常無法直接存取公開的 npm registry 伺服器,因此經常無法順利的下載 npm 套件。若透過 Azure Artifacts 的 Feed 來下載套件的話,就只要設定一次防火牆,網路管制方面會比較單純,因此較適合企業使用。若再開啟 Upstreams Sources 功能,就能透過單一 Registry 存取所有包含 npm registry 上的 npm 套件。
  2. 只要是透過 Upstreams Sources 下載的 npm 套件,一定會保留一份副本在 Azure Artifacts 的 Feed 之中,而且無法刪除。這樣一來,就算是 npm registry 伺服器上的套件被刪除了,你的專案還是可以順利的下載到套件,確保企業的老舊專案在多年以後還是能夠順利的進行 CI 建置。
  3. 透過 Upstreams Sources 去其他 Registry 下載套件可以設定權限,因此可以限制哪些套件可以被下載,哪些套件不可以被下載,這樣一來就能夠避免開發人員在專案中使用不合規定的套件,因此企業的合規性也更容易實現。

不過,啟用 Upstreams Sources (上游來源) 也有 1 個不算太嚴重的缺點:

  1. 所有透過 Upstreams Sources 下載的套件,一定會保留一份在 Feed 之中,而且無法刪除,所以經年累月之下,很容易就會累積數千到數萬個套件在上面,而且這些套件會跟企業自行發行到 Feed 之中的套件混在一起,感覺上就有點混亂。

目前我們多數的 Azure DevOps 顧問客戶,大多數都有啟用 Upstreams Sources 功能。

不過,我自己公司的 Feed 並沒有啟用 Upstreams Sources 功能,因為我們的 Feed 主要用來儲存幫客戶開發的客製化 npm 套件,我不想讓 Feed 看起來非常混亂,而套件少也比較容易瀏覽。

你在 Azure Pipelines 的 Build Pipeline 使用的身分識別

Azure Pipelines 的 Build Pipeline 有分兩種不同的身分識別(Identity)來執行 Pipelines,因此 Pipeline 在執行時,會根據你的身分識別來決定你可以存取哪些資源:

  1. 集合範圍的身分識別 (A collection-scoped identity)

    可以存取整個專案集合下的所有專案 (也就是整個 Azure DevOps Services 組織中的所有專案)

  2. 專案範圍的身分識別 (A project-scoped identity)

    只能存取當前專案的資源

基本上,我們在執行 Pipeline 的時候,多多少少都會需要存取 Azure DevOps 中各種不同的服務,例如: Azure Repos、Azure Artifacts、Azure Boards、...等等,而這些服務都會有不同的權限可以設定,因此 Azure Pipelines 的身分識別就是用來控管你可以存取哪些服務的權限。

因此,在授權時,你就應該要知道這兩種不同的身分識別分別叫什麼名字,否則你根本不知道該如何授權:

  1. 集合範圍的身分識別 (A collection-scoped identity)

    身分識別的名稱為: Project Collection Build Service (你的組織名稱)

  2. 專案範圍的身分識別 (A project-scoped identity)

    身分識別的名稱為: 你的專案名稱 Build Service (你的組織名稱)

我以 Azure Artifacts 的 Feed Settings 為例,我公司的組織名稱為 miniasp,因此你可以看到下圖的設定:

image

這裡的 Project Collection Build Service (miniasp) 預設就是加入到 Feed 的 Permissions (權限) 之中的,這意味著預設所有透過 集合範圍的身分識別 (collection-scoped identity) 執行的 Pipelines,都可以輕鬆的直接存取這個 Feed!不過,若使用 專案範圍的身分識別 (project-scoped identity) 執行的 Pipelines,就必須要另外授權才行。

你的 Azure Artifacts 的 Feeds 所在的組織是否跟 Build pipelines 位於同一個組織

由於 Azure DevOps Services 的設計,主要就是以「組織」為單位進行管理,因此當你的 Azure Artifacts 所屬的組織,跟 Azure Pipelines 在跑的專案是同一個組織時,就會比較容易設定權限,因為你只需要授權給 Project Collection Build Service (你的組織名稱)你的專案名稱 Build Service (你的組織名稱) 就可以了。

但如果是「不同組織下」的 Pipeline 要存取不同組織的 Azure Artifacts 的 Feeds 時,就會比較麻煩,因為你必須要知道對方的組織名稱、Feed URL、認證資訊(Credentials),才能順利的存取 Feed 的內容。但其實這種條件下的設定,就等同於在存取第三方 Custom Registry 伺服器,其實是一樣的。

Azure Pipelines 的 Build pipeline 選用的 Task 是哪一個

我的目的只是想要 npm install 成功而已,而實際上我們有兩種不同的選擇來執行 npm install 命令:

  1. 使用 npm task 執行 npm install

    這個 Task 是由微軟官方提供的,因此在設定上會比較簡單,而且可以直接指定要使用哪個 Feed 來存取 npm 套件。

    但這看起來簡單的背後,卻潛藏著各種地雷與陷阱,我不能說這個 npm task 不好用,他其實很好用,但僅適用於簡單的情境,一旦遇到複雜的情境,就會讓人覺得很頭痛,因為執行失敗的原因你不一定有辦法理解,而且很難 Debug!

    我這次就是敗在使用這個 Task 來執行 npm install 動作!😒

  2. 使用 Command line task 執行 npm install

    這個 Task 就是直接執行 Command Prompt (Windows) 或 Shell (Linux) 而已,跟你在本機開發環境執行 npm install 命令一樣,執行時不會有其他所謂「貼心」的額外設計,因此你可以完全控制執行的過程,而且可以直接看到執行的結果,Debug 起來也比較容易。(我認為啦)

    要是我一開始就是用 Command line task 來執行 npm install 的話,搞不好 5 分鐘我就把 Build pipeline 設定好了!(但也就不會有這篇文章出現了)😅

我簡單摘要一下這兩個 Tasks 的優缺點:

選用 Task 類型 優點 缺點
使用 npm task 不用事先建立 PAT 金鑰,只要設定好 Feed 授權就能跑 很有機會踩地雷
使用 Command line task 寫出來就很穩定 撰寫指令有門檻,而且需要事先建立 PAT 金鑰

使用 npm task 來安裝套件時,是否使用 npm authenticate (for task runners) task 來提供認證

這個 npm authenticate (for task runners) task 主要就是用來幫你設定 .npmrc 之用的,方便的幫你設定 npm registry 的認證資訊上去,理論上來說,你只要設定好 .npmrc 之後,就可以順利的存取 Azure Artifacts 的 Feed 上面的 npm 套件。

npm authenticate (for task runners) task

但是,這個 Task 的說明中,有一段話讓我覺得很奇怪:

Don't use this task if you're also using the npm task. Provides npm credentials to an .npmrc file in your repository for the scope of the build. This enables npm task runners like gulp and Grunt to authenticate with private registries.

什麼?當你想用 npm task 的時候,不要用這個 npm authenticate (for task runners) task 嗎?那我要怎麼存取 Azure Artifacts 的 Feed 呢?😱

image

那是因為在 npm task 之中,就已經內建的 Registry 的認證功能,他有兩個選項:

  1. Registries in my .npmrc

    直接使用 .npmrc 中設定好的認證資訊,並且你也可以選擇性的透過 Service Connection 來提供認證資訊,這樣一來,你就可以在 .npmrc 中設定多個 Registry 的認證資訊。

    這裡最雷的地方,就是當你在透過 npm task 的 npm install 執行之前,先自己手動將 Registry 的認證資訊加入到 .npmrc 之中,這個 Task 在執行時會自動將 .npmrc 中的認證資訊清除掉,然後再重新透過 Service Connection 取得到的 PAT 寫入到 .npmrc 之中。但是,當你沒有設定 Service Connection 的話,你就會發現你的 npm install 根本抓不到 Feed 之中的 npm 套件,你會得到 HTTP 403 未授權的錯誤,我的兩小時就是被這個奇葩的設計擺了一道!🔥 😭

    image

  2. Registries I select here

    指定要使用哪個 Azure Artifacts 的 Feed 來存取 npm 套件。

    image

    這個選項也有一個地雷,那就是他會強迫你只能使用一個 Registry 下載套件,所以你的 Feed 一定要設定 Upstreams Sources (上游來源) 才行,若沒設定的話,你在透過 npm install 下載套件的時候,很容易得到 HTTP 404 找不到套件的錯誤。

所以單獨使用 npm task 來安裝套件,無論什麼選項你都有機會踩到地雷。因此,我建議你還是使用 Command line task 來執行 npm install 命令比較直覺些。畢竟你在本機可以跑得命令,用 Command line task 通常也都能成功執行!

然而,我還是去嘗試了用 npm authenticate (for task runners) task 來替我的 .npmrc 加入必要的認證,並且研究出了「正確」的用法!

首先,微軟所說的 Don't use this task if you're also using the npm task. 其實並不正確,因為你確實有一種情況下可以透過 npm authenticate (for task runners) task 搭配 npm task 來執行,以下兩個條件符合就可以用:

  1. 你的 package.json 使用到了兩個不同的 npm registry 來下載套件

    例如: Azure Artifacts 的 Feed 使用了 @miniasp 這個 Scope,外加 public npm registry 用來下載大部分公開的套件。

    我的 如何正確設定 Azure Artifacts 的 npm 套件來源 (Feeds) 詳述了如何調整你的 .npmrc 如何設定某個 Scope 去 Feed 下載套件並通過驗證,這裡就不再贅述。

  2. 你要使用 npm task 來執行 npm install 命令,而且選用 Registries in my .npmrc 與不能使用 Service Connection 設定。

    注意:如果你是自行編輯 .npmrc 檔,先加入了 Feed 的認證資訊,然後才執行 npm tasknpm install 的話,就會掛掉!為什麼呢?因為你在 Feed 的 Connect to feed 所看到的 Setup credentials 設定值 (如下圖示),跟透過 npm authenticate (for task runners) task 寫入 .npmrc 的格式不一樣。只有透過 npm authenticate (for task runners) task 寫入 .npmrc 的認證格式,才能順利使用 npm tasknpm install 來安裝套件。就是必須這麼剛好的組合,才能正常運作。 😅

    image

    使用 npm authenticate (for task runners) task 寫入 .npmrc 的認證格式如下:

    //[orgname].pkgs.visualstudio.com/_packaging/[feedname]/npm/registry/:_authToken=***
    

    Azure Artifacts 的 Feed 提供的 Connect to feed 顯示的設定值如下:

    ; begin auth token
    //[orgname].pkgs.visualstudio.com/_packaging/[feedname]/npm/registry/:username=[orgname]
    //[orgname].pkgs.visualstudio.com/_packaging/[feedname]/npm/registry/:_password=[BASE64_ENCODED_PERSONAL_ACCESS_TOKEN]
    //[orgname].pkgs.visualstudio.com/_packaging/[feedname]/npm/registry/:email=npm requires email to be set but doesn't use the value
    //[orgname].pkgs.visualstudio.com/_packaging/[feedname]/npm/:username=[orgname]
    //[orgname].pkgs.visualstudio.com/_packaging/[feedname]/npm/:_password=[BASE64_ENCODED_PERSONAL_ACCESS_TOKEN]
    //[orgname].pkgs.visualstudio.com/_packaging/[feedname]/npm/:email=npm requires email to be set but doesn't use the value
    ; end auth token
    

    根本不一樣啊!😅

另外,使用 npm authenticate (for task runners) task 還有一個注意事項,這裡的 Credentials for registries outside this organization/collection 欄位,主要是設定 Service Connection 來提供 npm registry 的認證資訊,你其實可以完全不用設定,因為他預設會使用 Build Pipelines 的身分識別(Identity)來執行獲取 Token 的任務,因此你只要確保你的集合範圍的身分識別專案範圍的身分識別有權限存取 Azure Artifacts 的 Feed 就可以了。以下是設定授權的步驟:

image

image

image

image

你的 Node 專案使用 npm 套件的方法

你的 Node 專案使用 npm 套件的方法也可以分成兩類:

  1. 採用單一套件來源 (整個 npm install 過程只會存取一個 Registry)

    例如只從公開的 npm registry 抓套件,或是只從 Azure Artifacts 的 Feed 抓套件。

    這樣的設定比較單純。

  2. 採用混合套件來源 (整個 npm install 過程只依據不同的 Scope 存取不同的 Registry)

    我們在 package.json 設定檔中,通常有為數眾多的套件與相依套件需要安裝,而企業自行開發的套件通常會用 Scope 來做區隔,但你並不會在 package.json 去定義多個不同的 registry 來源,那是 .npmrc 的工作,例如:

    registry=https://registry.npmjs.org/
    @miniasp:registry=https://ORGNAME.pkgs.visualstudio.com/_packaging/FEEDNAME/npm/registry/
    

    上述設定的意義就是,只要是 @miniasp Scope 的套件,一律從 https://ORGNAME.pkgs.visualstudio.com/_packaging/FEEDNAME/npm/registry/ 抓取套件,你甚至可以設定多組不同的套件來源。

    我公司就是選擇這種。

你的 npm registry 認證方式

關於 npm registry 認證方式也很單純,就分「不用認證」與「需要通過驗證」這兩種而已。

  1. 使用 Public source 抓取 npm 套件

    這種通常都是不用認證的。

  2. 使用 Private source 或 Custom registry 抓取 npm 套件 (需通過認證)

    這種包含你自行架設的 npm registry 或是不同組織下的 Azure Artifacts 的 Feed,都是需要通過認證才能存取的。

透過 Service Connection 建立 npm 連線的認證方式

透過 Service Connection 建立 npm 連線有兩種認證方式:

  1. Username and Password
  2. Authentication Token 或 Personal Access Token (PAT)

image

總結

雖然我整理出這麼多情境,但我們這次的專案遇到的情境組合與解決方案是這樣的:

  • 你的 Azure Artifacts 的 Feed 的範圍:組織層級的 Feed (Organization-scoped Feed)
  • 你的 Azure Artifacts 的 Feed 是否有設定 Upstreams Sources (上游來源):沒有設定
  • 你在 Azure Pipelines 的 Build Pipeline 使用的身分識別:使用 Project-scope 身分識別
  • 你的 Azure Artifacts 的 Feeds 所在的組織是否跟 Build pipelines 位於同一個組織:相同組織
  • Azure Pipelines 的 Build pipeline 選用的 Task 是哪一個:npm task
  • 是否使用 npm authenticate (for task runners) task 來提供認證:
  • 你的 Node 專案使用 npm 套件的方法:採用混合套件來源
  • 你的 npm registry 認證方式:Public source + Private source
  • 透過 Service Connection 建立 npm 連線的認證方式:
  • 你的 Azure Artifacts 的 Feed 的範圍:組織層級的 Feed (Organization-scoped Feed)

然而我也整理出各種可以成功設定的組合,你可以參考看看:

  • 需事先建立 PAT 與 PAT 權限範圍(Scope)

    • 成功).npmrc 先有正確的 Credential,然後透過 Command line task 安裝
    • 失敗).npmrc 先有正確的 Credential,然後透過 npm task 安裝
  • 需事先建立設定 Feed Permission

    • 僅使用 npm task (僅適用於採用單一套件來源的情況)

      • 成功).npmrc 缺少 Credential 並透過 npm task 自動載入 Credential 且有設定 Feed Permission
      • 成功).npmrc 缺少 Credential 並透過 npm task 透過 Service Connection 取得 Credential (不用設定 Feed Permission)
      • 成功).npmrc 缺少 Credential 並透過 npm task 透過 Registries I select here 取得 Credential 且有設定 Feed Permission
    • 使用 npm authenticate (for task runners) task (適用於各種情況,包含採用單一套件來源與混合套件來源的情況)

      • 搭配使用 npm task

        • 成功).npmrc 缺少 Credential 並透過 npm authenticate (for task runners) task 自動載入 Credential 且有設定 Feed Permission,然後透過 npm task 搭配 Registries in my .npmrc 且不設定 Service Connection 安裝
      • 搭配使用 Command line task

        • 成功).npmrc 缺少 Credential 並透過 npm authenticate (for task runners) task 自動載入 Credential 且有設定 Feed Permission,然後透過 Command line task 安裝

透過這篇文章的整理,幫助我釐清許多模糊不清的觀念,也讓我更加了解 Azure Pipelines 與 Azure Artifacts 的運作方式,這也是我寫這篇文章的主要目的。但我不期望有很多人看懂,畢竟這牽涉到太多不同技術的基礎知識,少了任何一個環節,都會讓你無法理解整個運作的流程。所以我才覺得 Azure Artifacts 真的是一套看似點單但又卻非常複雜的玩意!😅

還有,我強烈建議大部分的 CI 不要過度依賴 Azure Pipeliens 內建的 Tasks 來執行,這種「半黑箱」的工具,遇到問題都很難偵錯。為什麼說是「半黑箱」呢?因為其實所有內建的 Tasks 都是開放原始碼的,我好幾次都是去查原始碼才找到文件沒說的用法與成功避開設定錯誤的地雷。

Azure Pipelines Tasks (GitHub): https://github.com/microsoft/azure-pipelines-tasks/tree/master/Tasks

反正我還是建議多利用 Command line task 來寫腳本,雖然門檻是稍微高了一點,但在透過 ChatGPT 的幫助下,其實也沒那麼難了!😎

相關連結

留言評論