之前在《GitLab CI/CD 重複使用的元件:component》這篇文章,有簡單紀錄一下要怎麼把本來 .gitlab-ci.yml
裡面的 template 改成 component,然後也有提到要怎麼把官方提供的 component 搬到自己架設的 GitLab Server 上來使用。
那,如果找不到現成的 component 可以用的時候,該怎麼自己寫一個呢?在官方文件的《CI/CD components》裡面,就有提供滿完整的說明了~而在《CI/CD component examples》中,也有提供一個範例。
Heresy 這邊為了拿 CppCheck 來做 C++ 的程式碼掃描,也自己寫了一個 CppCheck-CI 的 component 出來。他的網址是:https://gitlab.com/kheresy/cppcheck-ci。
這邊就簡單紀錄一下該怎麼寫吧。
專案架構
首先,一個 GitLab 專案可以提供多個 component、最多可以到 30 個。而同一個專案裡的 component 會使用同樣的版本編號,所以如果不同的 component 需要不同的版本的話,建議拆分成不同的專案。
如果要寫一個 component 的的話,在最基本的狀況下,就是要在「templates」這個目錄下、寫一個 YAML 檔案、來定義個別的 component。
而如果要放進 CI/CD Catalog 的話,則還有一些額外的要求;主要是:
- 要有
README.md
這個檔案 - 專案要有描述
- 專案本的
.gitlab-ci.yml
要能進行 component 的測試以及發布
專案的檔案配置也要有符合規範的結構,比較基本的專案大概會是下面的樣子:
├── templates/ │ └── my-component.yml ├── LICENSE.md ├── README.md └── .gitlab-ci.yml
其中,component 的腳本就是「templates
」下的「my-component.yml
」這個檔案。
基本上「templates
」這個資料夾是固定一定要有的,底下可以放多個 component 的 YAML 檔案、也可以用資料夾來包裝比較複雜的 component;像下面就是官方給的另一個例子:
├── templates/ │ ├── my-simple-component.yml │ └── my-complex-component/ │ ├── template.yml │ ├── Dockerfile │ └── test.sh ├── LICENSE.md ├── README.md └── .gitlab-ci.yml
這邊可以看到「my-complex-component
」就是一個資料夾、用來放置所有 component 相關的檔案。
Component 的語法
Component 的語法主要分成兩個部分:
- 以
spec
為最上層、定義輸入參數的規範 - 工作的定義
下面是官方給的簡單的示意:
spec:
inputs:
stage:
default: test --- component-job:
script: echo job 1
stage: $[[ inputs.stage ]]
「spec
」在 GitLab CI/CD 語法裡面算是相對新的語法(官方文件、應該是 17.0 才正式啟用),屬於「header keyword」(官方文件)的一種;不過目前 header 應該也只有 spec
一種就是了。
Header 區段必須要在 YAML 的最前方、和 script 的本體之間要透過「---
」做區隔。
「spec
」下目前也僅有「inputs
」這個代表輸入參數的設定,底下就是接列舉出要讓使用者可以輸入的參數的名稱,像上面的例子裡面,就是只有一個名為「stage
」的參數、然後它的預設值是「test
」。
這邊除了「default
」外,也還有可以針對輸入參數加入一些限制和描述,這部分就之後有機會再提了;如果有興趣的話,可以參考官方的《CI/CD inputs》這份文件。
在「---
」後面的區段就和以前的寫法一樣了,基本上就是定義出要進行的工作。
而在這邊如果要使用前面定義的輸入參數 stage
的話,則是要透過「$[[ inputs.stage ]]
」這樣相對比較複雜的形式(interpolation format?);這邊其實也還可以針對變數做一些特別的處理,這邊也是等之後有機會再提。
在這個例子裏面,component-job
這個工作就只有輸出「job 1」這樣的字串,然後 stage
的用處,則是用來控制這個工作會被放在整個 pipeline 哪個階段而已。
CppCheck 的例子
上面是一個沒有實際作用的 component,而以 Heresy 自己寫的 cppcheck-ci 來說,component 的 yaml 檔案則會像下面的樣子:
spec:
inputs:
stage:
default: test
path:
default: "./"
description: The path of source code, can assign multiple path
flags:
default: "--std=c++20"
description: The flags of CppCheck
parallel:
default: "-j $(nproc --all)"
description: Process in parallel --- cppcheck:
stage: $[[ inputs.stage ]]
image: python:3-slim
before_script:
- apt-get update; apt-get upgrade -y; apt-get install cppcheck -y
- pip install cppcheck-codequality
script:
- cppcheck --xml $[[ inputs.flags ]] $[[ inputs.parallel ]] $[[ inputs.path ]] 2> cppcheck.xml
- cppcheck-codequality
artifacts:
reports:
codequality: cppcheck.json
這邊的檔名是「templates/cppcheck.yml
」(連結)。
可以看到,在 spec.inputs
裡面,定義了四個變數、分別是 stage
、path
、flags
、parallel
,都各自有個別的預設值和描述。
下面則是定義了一個名為「cppceck
」的工作,會基於 python:3-slim
這個 Docker 映像檔,先透過 apt 安裝 cppcheck、然後再透過 pip 安裝 cppcheck-codequality;後者是用來將 cppcheck 產生的 XML 報告轉換成 GitLab 能接受的 Code Quality 報告的 JSON 格式用的。
理論上這部分其實應該打包成一個 Docker 映像檔、然後針對不同版本做紀錄會比較好;不過這邊算是偷懶,每次都重新安裝了。
真正掃描的部分,則是先透過 cppcheck 進行掃描、讓他產生 cppcheck.xml
檔。之後再呼叫 cppcheck-codequality
、讓他把 cppcheck.xml
轉換成 cppcheck.json
。
最後,則是將 cppcheck.json
作為 Code Quality 的報告、以 artifact 的形式傳遞給 GitLab Server 做分析。
直接使用
這樣寫完,如果不考慮要發布到 CI/CD Catalog 的話,那其實已經可以用了。
只要在其他的專案裡面的 .gitlab-ci.yml
中,加入:
include:
- component: $CI_SERVER_FQDN/kheresy/cppcheck-ci/cppcheck@main
inputs:
stage: test
path: ./
這樣他就會去把上面定義的 cppcheck.yml
的工作匯入、並把設定的 stage
和 path
帶進去了~
而這邊 component 最後是「@main
」,代表是去使用 main 這個分支的版本。這裡也可以換成其他分支名稱、tag 的名稱、或是 commit 的 SHA。如果想要用最新版本的話,則也可以寫 ~latest
;不過這個應該是要在有定義版本的狀況下才能用就是了。
另外,這邊的「$CI_SERVER_FQDN
」是 GitLab 預先定義的變數、代表 GitLab 的主機。而 component 基本上應該沒辦法用別台主機的,這點可能也得注意一下。
發佈到 CI/CD Catalog
而如果想要把寫好的 component 發佈到 GitLab 的 CI/CD Catalog 頁面、讓站上的使用者都可以更方便地使用的話,基本上要先做的事情有:
- 把專案設定成 CI/CD Catalog project
- 在專案的「Settings」、「General」裡面,展開「Visibility, project features, permissions」後,勾選「CI/CD Catalog project」之後,按下「Save changes」來儲存設定。
- 加入
README.md
- 由於在發布到 CI/CD Catalog 的時候,會把專案根目錄下的
README.md
作為 component 的說明,所以建議是要針對 component 寫一下說明、使用方法、以及輸入參數的定義。寫法的話建議可以參考官方的 component、這樣應該可以讓其他使用者更容易閱讀。 - 加入專案描述(Project description)
- 應該也是在發佈到 CI/CD Catalog 時必要的,因為這個描述的內容也會被帶到 CI/CD Catalog 裡面。
再來,要讓他可以方便地進行發布的話,比較好的方法應該是透過 CI/CI pipeline 來進行自動發布;而一般來說,這邊的 .gitlab-ci.yml
會建議針對 component 進行測試、確認可以正常運作後再發布。
下面是參考官方的東西寫出來的一個版本:
workflow:
rules:
- if: $CI_MERGE_REQUEST_ID
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH include:
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/cppcheck@$CI_COMMIT_SHA
inputs:
stage: test
path: test stages: [test, release] # If we are tagging a release with a specific convention ("v" + number) and all # previous checks succeeded, we proceed with creating a release automatically. create-release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
rules:
- if: $CI_COMMIT_TAG =~ /\d+/
script: echo "Creating release $CI_COMMIT_TAG"
release:
tag_name: $CI_COMMIT_TAG
description: "Release $CI_COMMIT_TAG of components repository $CI_PROJECT_PATH"
一開始的 workflow
是用來限制這個 pipeline 只有在特定的狀況下才會執行(tag、MR、主線);不加也可以,只是會變成每次 push 都會執行。
再來的 include
則就是引用自己寫的這個 component、用來建立測試的工作。而這邊是指定他會去掃描 test
這個資料夾、所以在該資料夾下也會需要準備一些 .cpp
給他掃描。
而 create-release
則是抄官方 component 的寫法的(例如這個),他主要的工作是透過 GitLab 提拱的 release-cli
這個工具(docker 版)、來進行釋出。
這邊主要是透過「release.tag_name
」來指定版本,這邊是直接沿用 tag 的名稱。理論上之後只要有建立新的符合版本編號的 tag、就會在測試過後自動發布新版本了。
不過,官方用個 rules
感覺不是很嚴謹、這個寫法應該是 tag 名稱裡面有數字就會執行?但是在註解裡面又說是要「v + 數字」?
另外,官方文件其實也有提到、CI/CD Catalog 的版本編號應該要是 a.b.c
這個形式的「semantic version」(參考)。所以這邊只能說,這裡的註解和判斷條件感覺都不是很嚴謹了。
要建立一個簡單的 component 大概就是這樣了?至少 Heresy 這邊看來應該是可以正常使用的。
不過就像前面說的,比較好的方法,其實可能是把 CppCheck 包成一個 Docker 映像檔、並標記版本,這樣會比較好控制就是了。但是這個就是後續改進的部分了。