建立一個 GitLab CI/CD component

| | 0 Comments| 11:25|
Categories:

之前在《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 裡面,定義了四個變數、分別是 stagepathflagsparallel,都各自有個別的預設值和描述。

下面則是定義了一個名為「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 的工作匯入、並把設定的 stagepath 帶進去了~

而這邊 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 映像檔、並標記版本,這樣會比較好控制就是了。但是這個就是後續改進的部分了。

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *