學習目標
- 知道每個 job 都是全新的容器,檔案不會自動留到下一個 job。
- 會用
artifacts:paths保存檔案、expire_in設保存期限。 - 會用
dependencies控制要下載哪些產物。 - 會用
reports:junit讓測試結果顯示在 GitLab 介面。
觀念
build ──(dist/ 上傳到 GitLab)──▶ test 下載 dist/ ──▶ 產生 report.xml
└▶ Tests 分頁
report:dependencies: [],不下載任何東西| 設定 | 說明 |
|---|---|
artifacts:paths | 要保存的檔案或資料夾,路徑相對於專案根目錄 |
artifacts:expire_in | 保存多久,例如 1 week;不寫就用執行個體的預設值 |
artifacts:when | on_success(預設)、on_failure、always |
artifacts:reports:junit | 測試報告,會出現在 pipeline 與 MR 上 |
dependencies | 不寫=下載前面所有階段的產物;[]=都不要 |
artifacts 與 cache 的差別
| artifacts | cache(Lv.5) | |
|---|---|---|
| 用途 | 傳遞結果:建置產物、報告 | 加速過程:套件下載 |
| 可靠性 | 一定拿得到 | 盡力而為,可能沒命中 |
| 可下載 | 可以 | 不行 |
動手做
- push 後到
buildjob 頁面點「Browse」,看得到dist/index.html。 - 打開 pipeline 的「Tests」分頁,會看到 2 項測試通過。
- 把
build.py裡的<h1>拿掉,test會失敗,報告裡看得到失敗原因。
常見錯誤
- 產物路徑寫錯,job 成功但紀錄出現
No files to upload。 - 把
node_modules這類大資料夾當 artifacts:應該用 cache。
下一關
Lv.4 學變數:內建變數、自訂變數、在 job 之間傳值。
範例檔案
資料夾 lv03-artifacts/ 的全部檔案。.gitlab-ci.yml 可以直接複製到自己的專案根目錄執行。
lv03-artifacts/.gitlab-ci.yml
lv03-artifacts/.gitlab-ci.yml
# Lv.3 Artifacts:把 job 產生的檔案保存下來,交給後面的 job 或讓人下載
# 每個 job 都在全新的容器裡執行,檔案不會自己留下來,要靠 artifacts 傳遞。
stages:
- build
- test
- report
default:
image: python:3.13-alpine
build:
stage: build
script:
- python3 lv03-artifacts/build.py
artifacts:
name: "lv03-site-$CI_COMMIT_SHORT_SHA"
paths:
- lv03-artifacts/dist/
expire_in: 1 week # 到期自動刪除,省空間
test:
stage: test
# dependencies:只下載指定 job 的產物。不寫=下載前面所有階段的產物
dependencies: [build]
script:
- python3 lv03-artifacts/test_build.py
artifacts:
when: always # 測試失敗也要保存報告
paths:
- lv03-artifacts/report.xml
reports:
junit: lv03-artifacts/report.xml # 在 pipeline 的 Tests 分頁顯示結果
expire_in: 1 week
report:
stage: report
dependencies: [] # 空陣列=不下載任何產物,job 啟動更快
script:
- test ! -d lv03-artifacts/dist && echo "dependencies 是空陣列,所以這裡沒有 dist/"
- echo "要下載產物:pipeline 頁面右側的下載按鈕,或 job 頁面的 Browse"lv03-artifacts/build.py
lv03-artifacts/build.py
"""Lv.3 建置腳本:產生一個小網站到 dist/,模擬真實專案的建置產物。"""
import os
from pathlib import Path
DIST = Path(__file__).parent / 'dist'
DIST.mkdir(exist_ok=True)
sha = os.environ.get('CI_COMMIT_SHORT_SHA', 'local')
(DIST / 'index.html').write_text(
'<!doctype html><html lang="zh-Hant"><meta charset="utf-8">'
f'<title>Lv.3 產物</title><h1>Lv.3 artifacts 範例</h1><p>版本 {sha}</p></html>\n',
encoding='utf-8')
(DIST / 'version.txt').write_text(sha + '\n', encoding='utf-8')
print('已產生:', ', '.join(sorted(p.name for p in DIST.iterdir())))lv03-artifacts/test_build.py
lv03-artifacts/test_build.py
"""Lv.3 測試:檢查 build 產物,並輸出 JUnit XML 讓 GitLab 顯示測試結果(只用標準函式庫)。"""
from pathlib import Path
import sys
import xml.etree.ElementTree as ET
HERE = Path(__file__).parent
DIST = HERE / 'dist'
def check_index():
html = (DIST / 'index.html').read_text(encoding='utf-8')
assert '<h1>' in html, '首頁沒有標題'
def check_version():
assert (DIST / 'version.txt').read_text(encoding='utf-8').strip(), 'version.txt 是空的'
cases = [check_index, check_version]
suite = ET.Element('testsuite', name='lv03-build', tests=str(len(cases)))
failed = 0
for case in cases:
node = ET.SubElement(suite, 'testcase', classname='lv03', name=case.__name__)
try:
case()
print('通過', case.__name__)
except (AssertionError, OSError) as err:
failed += 1
ET.SubElement(node, 'failure', message=str(err))
print('失敗', case.__name__, err)
suite.set('failures', str(failed))
ET.ElementTree(suite).write(HERE / 'report.xml', encoding='utf-8', xml_declaration=True)
sys.exit(1 if failed else 0)