透過 GitLab API 為 Project 設定指定的 GitLab Runner

GitLab Doc

本文只有一個重點,就是介紹可以使用 GitLab API 為 Project 設定 GitLab Runner,如果是使用工具前都會查閱說明書的資深 GitLab 使用者,可以直接轉身離開喔(喂~哪有文章一開頭就把人趕走的~)

(本文撰寫時,使用的 GitLab 環境為 14.0。) (本文同步發表於 Medium。)

前情提要

之前在 GitLab Taipei User Group 中有一位苦主表示遇到了一個狀況,他必須要將指定的 GitLab Runner 分配給超過百個 Project 使用,想詢問有沒有比較簡單的方法?

一般來說如果有 Runner 需要同時供應給多個 Project 使用,通常會建議將 Runner 建立為 Group Runner。如此一來在相同 Group 內的所有 Project 皆能直接取用此 Runner,唯一的限制就是 Project 必須隸屬在相同的 Group。因此對於苦主來說,這個解法他會需要將超過百個 Project 全部搬家至 Group 內,因此不太適用。

另一個方案是,假如你是自架 GitLab Server,則可以透過 Admin 管理者,架設 Shared Runner,便能夠如同 gitlab.com 那樣,直接提供不限 User、Project 皆能任意使用的 Runner。當然要說缺點就是 Shared runner 預設是全開放給所有人皆能使用的 Runner,因此如果你又想要做出額外的權限控管,同樣又要另外想辦法。不幸的是印象中苦主似乎是使用 gitlab.com,GitLab 官方當然沒開放權限讓使用者自行在 gitlab.com 上架設 Shared runner。

那麼除了上述兩個方法之外,還有別的選擇嗎?

於是艦長當時想到的方案是——「也許,寫個簡單的程式,foreach 跑個迴圈,去透過 GitLab API 為每個 Project 設置 Runner。」

當然這可能也不是一個最佳的解決方案,不過也許可以考慮考慮?畢竟 GitLab 提供了不少 API 可以運用,透過 API 讓我們能實現各種自動化工作:

GitLab 有 API 可以根據 User 權限直接撈出所有的 Project id GitLab 有 API 可以查詢 Runner id GitLab 有 API 可以替指定的 Project 直接 Enable 特定 id 的 Runner 透過上述三種 API,要實現「寫個簡單的程式,foreach 跑個迴圈,去透過 GitLab API 為每個 Project 設置 Runner。」並不難。

另外幾個不考慮 Group 與 Shared Runner 的原因則是:

  • Group Runner 的限制是必須隸屬在同一個 Group,如果苦主真的全部搬家到同一個 Group,萬一哪天 Project 又要歸屬為不同 Group 時該怎麼辦?
  • 前面有描述過的,Group Runner 預設是直接 Enable 給 Group 之下所有 Project,如果你想要 Disable,還需要自己額外處理。
  • 一樣前面也說明過,Shared Runner 也與 Group Runner 的狀況相似,預設是 Enable 給所有 Project,如要 Disable 也同樣要自己額外處理。

(Shared Runner 預設是 Enable。) (Shared Runner 預設是 Enable。)

(Group Runner 預設也是 Enable。題外話 GitLab 這裡的 UI 怎麼不改成與 Shared Runner 一致呢?) (Group Runner 預設也是 Enable。題外話 GitLab 這裡的 UI 怎麼不改成與 Shared Runner 一致呢?)

操作步驟

根據上面的前情提要,我們會使用到三種 GitLab API,分別如下:

  • 根據 User 權限直接撈出所有的 Project id(List all projects)
  • 查詢 Runner id(List owned runners)
  • 替指定的 Project 直接 Enable 特定 id 的 Runner(Enable a runner in project)

先取得足夠權限之 Access Token

在使用 API 之前,我們需要先取得足夠權限的 Token,最容易取得且方便使用的是 Personal access tokens,當然方便的另一面就是請小心保管,避免被人亂用。

使用者可以在自己的 User Settings > Access Tokens 來產生一組新的 Personal access tokens

(記得要勾選 api 權限。) (記得要勾選 api 權限。)

產生出來的 Access Token 要趕快複製備存,之後無法再複查,只能重新產生。

有了 Access Token,就可以開始使用 GitLab API。

取得所有的 Project id

這裡要使用的是 API - List all projects

API URL 為

GET /projects

curl 來使用 API 則指令如下。

curl --header "PRIVATE-TOKEN: <personal_access_token>" "https://gitlab.com/api/v4/projects"

直接送出,應該就會得到類似下圖這樣,滿畫面的 Project 資訊。

(當 Project 數量很多時,有可能會塞滿你的 Terminal 捲動好幾頁。) (當 Project 數量很多時,有可能會塞滿你的 Terminal 捲動好幾頁。)

由於 API - List all projects 有可能會一次提供太多資料,所以我們要稍微過濾、排序並分頁一下會比較好。

curl --header "PRIVATE-TOKEN: <personal_access_token>" "https://gitlab.com/api/v4/projects?simple=true&search=<search_keyword>"

多加上兩個參數 simplesearchsimple 會讓 API 回傳較精簡的內容(但其實還是很多),而 search 就如你想像的那樣,只會回傳吻合此 Keyword 的 Project 資料。

(這裡直接偷懶用檔案大小來比較有沒有加上 simple=true 資料量差了多少。) (這裡直接偷懶用檔案大小來比較有沒有加上 simple=true 資料量差了多少。)

這邊要補充一點,如果你是使用 gitlab.com,光是 search 可能並不足以找出你的目標 Project。因為 gitlab.com 上有著全世界使用者建立的 Public project,因此光是搜尋關鍵字,也會將這些與你無關但吻合關鍵字的 Project 一併搜尋出來。所以我們還要再補上其他的 API 參數 membershipowned

根據官方文件,這兩個參數分別可以幫我們過濾出確實屬於我們的 Project,至於該用哪一個,就看你的實際狀況了。

  • membership - Limit by projects that the current user is a member of.
  • owned - Limit by projects explicitly owned by the current user.

接著再加上 order_bysort 確保資料一致的排序。

curl --header "PRIVATE-TOKEN: <personal_access_token>" "https://gitlab.com/api/v4/projects?simple=true&search=<search_keyword>&owned=true&order_by=id&sort=asc"

最後可以考慮再補上 per_pagepage 控制每次要輸出多少資料及哪一頁的資料。

curl --header "PRIVATE-TOKEN: <personal_access_token>" "https://gitlab.com/api/v4/projects?simple=true&search=<search_keyword>&owned=true&order_by=id&sort=asc&per_page=<每頁幾筆資料>&page=<取出第幾頁的資料>"

舉例:
curl --header "PRIVATE-TOKEN: abcdefghijklmnop" "https://gitlab.com/api/v4/projects?simple=true&search=demoproject&owned=true&order_by=id&sort=asc&per_page=50&page=1"

集合上述所有的變數,我們就能按照自己的需要,控制 API 去撈出我們想要的 Project。由於回傳的資料是標準的 JSON 格式,因此就自己找個合適的方式解析並取得其中每一個 project 的 id 即可。

【小提醒】per_page 預設為 20,且至多只能設定為 100

其實 GitLab 還有另一個 Search API 也能用來搜尋 Project,而且回傳的每筆 Project 資料更少,似乎也適合用來取得目標 Project 的 id。但很可惜是 Search API 目前並沒有 membershipowned 這兩項參數,而且也只能 order_by=created_at,因此查找出來的結果很容易不合乎期待,所以這裡就不特別解釋 Search API,僅直接示範一個 curl 指令。

curl --header "PRIVATE-TOKEN: <personal_access_token>" "https://gitlab.com/api/v4/search?scope=projects&search=<search_keyword>&order_by=created_at&sort=asc&per_page=<每頁幾筆資料>&page=<取出第幾頁的資料>"

查詢 Runner id

第二個要使用的 GitLab API 是 List owned runners,用它來幫我們取得目標 GitLab Runner 的 id。

前面已經示範過 API 怎麼使用,這裡就直接切入重點,通常 GitLab Runner 我們都會設置 Tag,方便我們在 GitLab CI Pipeline 中為 CI Job 指配 Runner,所以我這裡就直接用 tag_list + search 來搜尋過濾我要找的 Runner。

curl --header "PRIVATE-TOKEN: <personal_access_token>" "https://gitlab.com/api/v4/runners?tag_list=<tag_name>&search=<search_keyword>"

舉個實際的範例,如果我要找 Tag 為 production 並且 Runner 的 description 有註記 ansible 2.10 的 Runner,那麼 curl 指令會類似如下。

curl --header "PRIVATE-TOKEN: abcdefghijklmnop" "https://gitlab.com/api/v4/runners?tag_list=production&search=ansible+2.10"

最後,其實有一個更簡單得知 GitLab Runner id 的方法,就是去 Settings > CI/CD > Runners 直接查看。

如下圖,以 gitlab.com 的 Shared Runners 為例,2072938 即是此 Runner 的 id。

GitLab Shared Runner id

替指定的 Project 直接 Enable 特定 id 的 Runner

最後是我們的重頭戲,使用 Enable a runner in project 這個 GitLab API,為目標 Project 啟用指定的 Runner。

這裡稍有不同,這次我們要使用 POST 來戳 API,API URL 為

POST /projects/:id/runners

一樣直接上 curl 指令。

curl --request POST --header "PRIVATE-TOKEN: <personal_access_token>" "https://gitlab.com/api/v4/projects/<project_id>/runners" --form "runner_id=<runner_id>"

同樣來個實際範例,如果目標 Project id 為 12345678,目標 Runner id 為 7654321,那麼 curl 指令會類似如下。

curl --request POST --header "PRIVATE-TOKEN: abcdefghijklmnop" "https://gitlab.com/api/v4/projects/12345678/runners" --form "runner_id=7654321"

透過程式跑迴圈一次為所有 Project 啟用 Runner

上面介紹完所有會使用到的 API 之後,最後還是來示範一下如何透過程式跑迴圈一次為所有 Project 啟用 Runner。

這裡就直接上 Code 啦,使用的程式語言是————————————Ansible (謎之音:Ansible 不是程式語言吧。)

- name: "Enable Runner for projects"
  hosts: "localhost"
  gather_facts: no
  become: no

  vars:
    # 如果你是自架 GitLab Server - gitlab_api_url 要改成你的 api url
    gitlab_api_url: https://gitlab.com/api/v4
    gitlab_access_token: <your_access_token>
    keyword: "<用來搜尋 Project 的關鍵字>"
    
    # 這裡是先查好 Runner id,直接填進來 
    runner_id: <事先查好的 Runner id>

  tasks:
  - name: Get projects data
    uri:
      url: "{{ gitlab_api_url }}/projects?search={{ keyword }}&simple=true&owned=true&order_by=id&sort=asc"
      method: GET
      headers:
        PRIVATE-TOKEN: "{{ gitlab_access_token }}"
      status_code: 200
      timeout: 90
    register: gitlab_projects_data
  
  - debug: msg="{{ item.id }}"
    with_items:
      - "{{ gitlab_projects_data.json }}"

  - name: Enable GitLab Runner
    uri:
      url: "{{ gitlab_api_url }}/projects/{{ item.id }}/runners"
      method: POST
      headers:
        Content-Type: "application/json"
        PRIVATE-TOKEN: "{{ gitlab_access_token }}"
      body_format: json
      body: '{"runner_id": "{{ runner_id }}"}'
      # 要注意有成功 POST 回傳的 status code 會是 201。
      status_code: 201
      timeout: 90
    with_items:
      - "{{ gitlab_projects_data.json }}"

只要執行上面的 Ansible playbook,填入正確的 Variables 就可以完成我們的目標。

【補充】gitlab.com 的使用者要注意 GitLab 官方對於 API traffic 是有 Limit 限制的,根據文件目前是 2,000 requests per minute,如果你預估會大量使用 API,記得注意一下這項限制。

結語

GitLab API 是個好東西,透過 API 可以實現許多有用的自動化工作。GitLab 官方除了不斷的在整合更多開源及第三方的服務,GitLAb API 也有持續在改版更新,這些都是能幫助使用者可以更方便使用 GitLab 的好功能,希望大家都能善用它,一起做個又懶又有生產力的工程師!

工商服務

如果你覺得艦長寫的文章對你有產生幫助,歡迎抖內(Donate)艦長,讓艦長可以將原本用來養家活口的餘力多分一些投入在社群分享上。咦,你說網頁上沒看到任何類似 Buy Me a Coffee 的連結?那不妨買一本艦長的著作《和艦長一起 30 天玩轉 GitLab》,不只能抖內艦長,還可以一併支持出版社與圖書通路商,一次抖內一舉數得喔!

參考資料

更多文章