在多人協作的軟體開發流程中,運用 Git 及 Branch 已經是一件再平常不過的事情了,想必大家多少都有經歷過多人開發的工作情境,在這樣的情境中,多位開發工程師會各自在不同的 Branch 開發功能,最後透過 Merge Request(MR)將程式碼進行合併。
在這樣的情境中,MR 肩負了重要的任務,它會是一個重要的資訊彙整、進度同步、討論、稽核⋯⋯的關鍵點。
既然 MR 如此重要,同時它又是團隊協作及軟體開發流程中經常發生的一件事,可以的話,我們應該要依據團隊實務上的需要,讓使用者可以更方便的在 MR 頁面彙整並瀏覽各種必要的資訊。
GitLab 原廠當然有注意到 MR 的重要性,因此在 GitLab 免費版中,MR 頁面已經有整合了多項必要的基本資訊。(當然付費版比起免費版擁有更多的整合。)
首先在 GitLab 的 MR 頁面,可以看到 Overview
、Commits
、Pipelines
與 Changes
四個分頁。後三者比較單純,分別用來讓使用者快速一覽當前的 MR 包含了哪些 Commits、觸發了哪些 Pipelines、以及實際上有哪些檔案被異動了。
在 Overview
的分頁中,則是資訊大彙整,包含了這項 MR 的基本資訊、最新一條 CI Pipeline 的執行狀態、MR 是否有通過 Code Reviewer 的 Approval、以及團隊夥伴是否有對此 MR 留下各種 Comments。
如上圖所示,在 MR 的 Overview
頁面上,有整合了最新一條 CI Pipeline 的執行狀態,但你只能知道 Pipeline 最終是成功還是失敗,如果你想要知道 CI Pipeline 內產生的更多資訊(例如:CI Job 產出的 Reports 內容),一般來說你只有兩個選擇:
- 付錢升級,啟用付費功能(如果有你需要的功能)
- 點擊連結,前往 CI Pipeline 查看對應的 CI Job Log 及直接瀏覽 Job Artifacts 的檔案
如果上面兩種方法你都不滿意,那也許你可以考慮一下本文將要介紹的第三種方法!即是運用 GitLab API,讓 CI Pipeline 自動將 CI Job 執行結果發佈到 MR 的 Comments 中,藉此實現在同一個 MR 頁面即可看到更多 CI Job 產生的重要資訊。
操作步驟
建立 Merge Request 的 CI Pipeline
首先這次實驗我們會使用一種特別的 CI Pipeline,即是 Merge Request 之 Pipeline,下面是一個簡單的示範,如何在 .gitlab-ci.yml
中做出此種 Pipeline。
auto-test:
stage: test
script:
- echo "假裝我有跑了某種自動化測試"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
在上面的 .gitlab-ci.yml
,我規劃了一個 CI Job,它的特別之處在於我設置了 rules:
,並且條件是 if: $CI_PIPELINE_SOURCE == "merge_request_event"
,這意味著只有發生 MR 時,這個 Job 才會被執行。
為了要讓它試跑一下,所以我先隨便建立了一個 Feature branch - demo-mr
,並且送出一個 Commit,接著建立 Merge request。
如上圖,在 Pipelines 頁面,我們會看到出現了一條 CI Pipeline,並且它會被加上 merge request
的藍色標籤,代表它與其他的 CI Pipeline 不同,是專門搭配 MR 的 Pipeline。
在有 MR 存在的狀況下,每當我在 Branch - demo-mr
送出一批新的 Commits,都會觸發 GitLab CI 執行這條 MR Pipeline;如下圖,隨著我 git push 兩個新的 commits 到 Branch - demo-mr
,該 MR Pipeline 就再次被驅動執行。
測試 GitLab API - Create new merge request note
接著我們要測試一下 GitLab API,這裡我們會用到的 API 是 Create new merge request note;這個 API 可以讓我們在目標 MR 新增一則 Comment。
先讓我們用手動的方式測試它。
- 先取得使用 API 時必備的 Access Token。這裡看你要使用的是 Personal、Project 或 Group access tokens 都可以。
- 取得 Project id。如果要手動取得 Project id,你可以進到該 Project 的入口頁,在 Project name 的下方就能看到啦。如下圖,Project id 即是
52056724
。 - 取得 Merge request id。如果要手動取得 MR id,你可以進到 MR 頁面,看一下網址或 MR 的編號即可。以下圖為例, MR id 即是
1
。 - 組合出所需的
curl
指令,就試著送出看看結果。# curl 範例 ## :id 換成你的 project id ## :merge_request_iid 換成你的 MR id ## <YOUR_ACCESS_TOKEN> 要換成你的 Token curl --request POST \ --url 'https://gitlab.com/api/v4/projects/:id/merge_requests/:merge_request_iid/notes' \ --header 'Authorization: Bearer <YOUR_ACCESS_TOKEN>' \ --header 'Content-Type: application/json' \ --data '{ "body": "Use the force Harry!!" }' # 以前面的內容為例 curl 範例 curl --request POST \ --url 'https://gitlab.com/api/v4/projects/52056724/merge_requests/1/notes' \ --header 'Authorization: Bearer 這就換成我的Token啦' \ --header 'Content-Type: application/json' \ --data '{ "body": "Use the force Harry!!" }'
如果 API 有執行成功,即可得到類似下圖的成果,順利在 MR 頁面新增了一則 Comment。
測試透過 GitLab API 在 MR Comment 送出多行內容與文字格式
成功使用 API 後,接著要來多一點變化,測試如何送出多行內容到 MR Comment 中。這裡就不賣關子了,要送出多行內容是可行的,只是你要自己在內容中加上 \n
換行符號。
# 繼續使用前面的範例
curl --request POST \
--url 'https://gitlab.com/api/v4/projects/52056724/merge_requests/1/notes' \
--header 'Authorization: Bearer 這就換成我的Token啦' \
--header 'Content-Type: application/json' \
--data '{
"body": "Use the force Harry!!\n\n——Gandalf"
}'
上面的範例會得到下圖的成果。
如果你有仔細看,你可能會感到奇怪,為什麼會是兩個 \n\n
?如果只是要換行,不是只要一個 \n
就夠了嗎?
這是因為 MR Comments 是支援 Markdown 格式的,如果你熟悉 Markdown 格式,你應該知道只有一個換行時,會變成什麼樣子,讓我們用真實的圖片說明。
如上圖,以純文字的模式編輯 Comment 時,看起來文字內容是有換行,形成兩行文字的排版,但在 Markdown 語法,它會直接將第二行的內容接續在第一行的後面,因此如果你希望的排版是第一行與第二行文字彼此是有間隔的,請記得要使用兩個 \n
。
既然 Comment 接受的是 Markdown 語法,這裡就讓我們再做一次實驗,透過 API 送一個 Markdown 語法撰寫的表格試試看。
# 繼續使用前面的範例
curl --request POST \
--url 'https://gitlab.com/api/v4/projects/52056724/merge_requests/1/notes' \
--header 'Authorization: Bearer 這就換成我的Token啦' \
--header 'Content-Type: application/json' \
--data '{
"body": "Here is a Markdown table:\n\n| Name | ID |\n|----------|----------|\n| Captain | 99 |\n| Cheng Wei | 9999 |"
}'
這次得到下面的成果。
在 CI Job 中使用 GitLab API
經過前面的實驗,差不多可以實際在 CI Job 內實作了,這裡就讓我延續上一篇文章的範例來當作實驗標的。
在上篇文章中,我嘗試將 OSV Scanner 的 Report 整合進 CI Pipeline 的介面,這次我打算將 Report 的內容,透過本文的方法直接放進 MR 頁面的 Comment 中。
一樣先上 .gitlab-ci.yml
。
# 沿用上篇文章的 CI Job,先完成 osv scanner
osv scanner:
image: chengweisdocker/osv-scanner-for-ci:1.4.3
stage: test
script:
- set +e
- cd $CI_PROJECT_DIR
- /root/osv-scanner -r --format=table --output=osv-report.txt .
- cat osv-report.txt
artifacts:
paths:
- osv-report.txt
# 補一個 rules,讓它也只會在 MR Pipeline 中執行
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# 接著準備下一個 CI Job 專門丟 GitLab API
push to MR:
image: python:3.12
stage: test
# 相依上一個 Job
needs:
- "osv scanner"
# 需要上一個 Job 的 artifacts
dependencies:
- "osv scanner"
script:
# 寫了一個簡單的 python,專門用來讀取 osv scanner 的 report
# 並將內容丟到 GitLab API,
# 因為只是 DEMO,我就偷懶把檔案直接存進同一個 Project 中了。
# pip install 裝一下 python 套件
- pip install requests
# 執行我的小程式來丟 GitLab API
- python post_api.py
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
由於用 shell 想要組合複雜的 json 內容會有一點辛苦,所以在上面的範例中,我是透過執行 python post_api.py
來幫我完成原本透過 curl
執行 API 的動作。
這個簡單的 python 程式碼如下:
# post_api.py
import os
import re
import requests
import json
# 從 ENV 中取得執行 GitLab API 所需的 Token
AUTH_TOKEN = os.environ.get("AUTH_TOKEN")
# 從 ENV 中取得 GitLab Project ID
CI_PROJECT_ID = os.environ.get("CI_PROJECT_ID")
# 從 ENV 中取得變數 CI_OPEN_MERGE_REQUESTS,
# 並且透過文字處理,從中取得 MR ID
ci_open_merge_requests = os.environ.get("CI_OPEN_MERGE_REQUESTS")
match = re.search(r'.*!(\d+)$', ci_open_merge_requests)
MR_ID = match.group(1)
# 讀取 osv-report.txt 的內容
with open('osv-report.txt', 'r') as f:
CONTENT = f.read()
# 組合出 request 的內容
url = f"https://gitlab.com/api/v4/projects/{CI_PROJECT_ID}/merge_requests/{MR_ID}/notes"
headers = {
"Authorization": f"Bearer {AUTH_TOKEN}",
"Content-Type": "application/json",
}
# 由於 report 的內容每一行寬度會超過 Comment 的寬度,
# 所以乾脆將內容塞進 Markdown 的 code fenced blocks 中
data = {
"body": f"```{CONTENT}```"
}
json_data = json.dumps(data)
response = requests.request("POST", url, headers=headers, data=json_data)
在前面的 Python 小程式中有從 ENV 取得 $AUTH_TOKEN
,因此我還需要在 Project settings 中,將具備使用 API 權限的 Access Token 設定為 CI/CD Variables。
最後,就讓 MR Pipeline 跑一下啦!
(查看 CI Job Log,我的 post_api.py
有成功執行!)
最後如下圖,成功將 OSV Scanner 的 Report 送進 MR 頁面的 Comments 啦!
小結
在這篇文章中,我們示範了如何透過 GitLab API 讓我們可以自由的將 CI Job 中必要的資訊彙整在 MR 頁面,在無需修改 GitLab 原始碼與額外付費的前提下,這算是一個尚可接受的折衷辦法。
一樣要提醒一下,本文中的範例仍不夠完美,只是一個簡單的示範,還有很多可以改進的地方,例如:
- 是否有必要拆分成兩個 CI Job 分別處理 OSV Scanner 與執行 GitLab API?
- 有沒有更方便與安全的方式可以執行 GitLab API?畢竟不同的 Reports 格式可能不同?本文最後使用 Python 來輔助執行 GitLab API 也會導致 CI Job 的執行環境需要相依 Python,這點可能也不太方便?
- 本文使用的 GitLab API 在
body
是有字數限制的,如何避免超過上限? - 只把 Reports 的資訊送進 MR Comment 是否足夠?是不是還應該加上更多的資訊,例如是哪一次的 Pipeline 或 CI Job 產生這些內容的並附上對應的超連結。
希望本文有激發到大家的創意,一起來思考一下到底在 MR 頁面,你會想要直接一覽哪些資訊呢?有沒有什麼你覺得很重要,但是在目前的 MR 頁面中是有所欠缺的?有任何想法都歡迎與我分享交流喔!