YAML 並不是一種新奇的語言,YAML 首次發表於 2001 年,距離現在已經過去差不多 20 個年頭。YAML 雖然不如 JSON、XML 之類的語言流行,應用也沒有那麼廣泛,但是 YAML 也有它的優勢。
一、簡介
YAML 是一種較爲人性化的數據序列化語言,可以配合目前大多數編程語言使用。
YAML 的語法比較簡潔直觀,特點是使用空格來表達層次結構,其最大優勢在於數據結構方面的表達,所以 YAML 更多應用於編寫配置文件,其文件一般以 .yml 爲後綴。
YAML 目前的官方全稱爲 “ YAML Ain't Markup Language(YAML 不是標記語言)”,但有意思的是,其實 YAML 最初的含義是 “ Yet Another Markup Language(還是一種標記語言)”。
目前 YAML 的最新版本爲 1.2(第三個版本),本文將以 YAML 1.2 的標準進行講解。
二、基本語法
大小寫敏感
- 就是字面上的意思
One: 1
one: 2
用縮進表示層級關係
- 縮進只能使用空格,不能用 TAB 字符
- 縮進的空格數量不重要,但是同一層級的元素左側必須對齊
# YAML
one:
two: 2
three:
four: 4
five: 5
// 以上的內容轉成 JSON 後
"one": {
"two": 2,
"three": {
"four": 4,
"five": 5
}
}
用 # 表示註釋
- 只支持單行註釋
# 我是註釋
# 我也是註釋
一個文件中可以包含多個文件的內容
- 用“ --- ”即三個破折號表示一份內容的開始
- 用“ ... ”即三個小數點表示一份內容的結束(非必需)
---
# 這是第一份內容
one: 1
# 其他內容...
...
---
# 這是第二份內容
two: 2
# 其他內容...
數據結構與類型
對象(Mapping)
表示以鍵值對(key: value)形式出現的數據
- 使用“冒號+空格”來分開鍵與值
# YAML
key: value
// JSON
"key": "value"
- 支持多層嵌套(用縮進表示層級關係)
# YAML
key:
child-key1: value1
child-key2: value2
// JSON
"key": {
"child-key1": "value1",
"child-key2": "value2",
}
- 支持流式風格( Flow style)的語法(用花括號包裹,用逗號加空格分隔,類似 JSON)
# YAML
key: { child-key1: value1, child-key2: value2 }
// JSON
"key": { "child-key1": "value1", "child-key2": "value2" }
- 使用問號“?”聲明一個複雜對象,允許你使用多個詞彙(數組)來組成鍵
# YAML
?
- keypart1
- keypart2
:
- value1
- value2
數組(Sequence)
- 一組以區塊格式(Block Format)(即“破折號+空格”)開頭的數據組成一個數組
# YAML
values:
- value1
- value2
- value3
// JSON
"values": [ "value1", "value2", "value3" ]
- 同時也支持內聯格式(Inline Format)來表達(用方括號包裹,逗號加空格分隔,類似 JSON)
# YAML
values: [value1, value2, value3]
// JSON
"values": [ "value1", "value2", "value3" ]
- 支持多維數組(用縮進表示層級關係)
# YAML
values:
-
- value1
- value2
-
- value3
- value4
// JSON
"values": [ [ "value1", "value2"], ["value3", "value4"] ]
標量(Scalars)
表示 YAML 中最基本的數據類型
字符串(String)
- 字符串一般不需要用引號包裹,但是如果字符串中使用了反斜槓“\”開頭的轉義字符就必須使用引號包裹
# YAML
strings:
- Hello without quote # 不用引號包裹
- Hello
world # 拆成多行後會自動在中間添加空格
- 'Hello with single quotes' # 單引號包裹
- "Hello with double quotes" # 雙引號包裹
- "I am fine. \u263A" # 使用雙引號包裹時支持 Unicode 編碼
- "\x0d\x0a is \r\n" # 使用雙引號包裹時還支持 Hex 編碼
- 'He said: "Hello!"' # 單雙引號支持嵌套"
// JSON
"strings":
[ "Hello without quote",
"Hello world",
"Hello with single quotes",
"Hello with double quotes",
"I am fine. ☺",
"\r\n is \r\n",
"He said: 'Hello!'" ]
- 對於多行的文字,YAML 提供了兩種特殊的語法支持
保留換行(Newlines preserved)
使用 豎線符“ | ”來表示該語法,每行的縮進和行尾空白都會被去掉,而額外的縮進會被保留
# YAML
lines: |
我是第一行
我是第二行
我是吳彥祖
我是第四行
我是第五行
// JSON
"lines": "我是第一行\n我是第二行\n 我是吳彥祖\n 我是第四行\n我是第五行"
摺疊換行(Newlines folded)
使用 右尖括號“ > ”來表示該語法,只有空白行纔會被識別爲換行,原來的換行符都會被轉換成空格
# YAML
lines: >
我是第一行
我也是第一行
我仍是第一行
我依舊是第一行
我是第二行
這麼巧我也是第二行
// JSON
"lines": "我是第一行 我也是第一行 我仍是第一行 我依舊是第一行\n我是第二行 這麼巧我也是第二行"
布爾值(Boolean)
- “true”、“True”、“TRUE”、“yes”、“Yes”和“YES”皆爲真
- “false”、“False”、“FALSE”、“no”、“No”和“NO”皆爲假
# YAML
boolean:
- true # True、TRUE
- yes # Yes、YES
- false # False、FALSE
- no # No、NO
// JSON
"boolean": [ true, true, false, false ]
整數(Integer)
- 支持二進制表示
# YAML
int:
- 666
- 0001_0000 # 二進制表示
// JSON
"int": [ 666, 4096 ]
浮點數(Floating Point)
- 支持科學計數法
# YAML
float:
- 3.14
- 6.8523015e+5 # 使用科學計數法
// JSON
"float": [ 3.14, 685230.15 ]
空(Null)
- “null”、“Null”和“~”都是空,不指定值默認也是空
# YAML
nulls:
- null
- Null
- ~
-
// JSON
"nulls": [ null, null, null, null ]
時間戳(Timestamp)
- YAML 也支持 ISO 8601 格式的時間數據
這裏使用 JavaScript 對象進行對比
# YAML
date1: 2020-05-26
date2: 2020-05-26T01:00:00+08:00
dete3: 2020-05-26T02:00:00.10+08:00
date4: 2020-05-26 03:00:00.10 +8
// JavaScript
date1: Tue May 26 2020 08:00:00 GMT+0800 (中國標準時間),
date2: Tue May 26 2020 01:00:00 GMT+0800 (中國標準時間),
dete3: Tue May 26 2020 02:00:00 GMT+0800 (中國標準時間),
date4: Tue May 26 2020 03:00:00 GMT+0800 (中國標準時間)
類型轉換
- YAML 支持使用嚴格類型標籤“!!”(雙感嘆號+目標類型)來強制轉換類型
# YAML
a: !!float '666' # !! 爲嚴格類型標籤
b: '666' # 其實雙引號也算是類型轉換符
c: !!str 666 # 整數轉爲字符串
d: !!str 666.66 # 浮點數轉爲字符串
e: !!str true # 布爾值轉爲字符串
f: !!str yes # 布爾值轉爲字符串
// JSON
"a": 666,
"b": "666",
"c": "666",
"d": "666.66",
"e": "true"
"f": "yes"
其他高級類型
YAML 也可以使用一些更高級的類型,但是並不一定兼容所有解析器,包括集合(Sets)、有序映射(Ordered Map)、十六進制數據(Hexdecimal)和二進制數據(Binary)。
本文將不會對這幾種類型進行講解,感興趣的讀者可以自行搜索研究。
數據重用與合併
- 爲了保持內容的簡潔,避免過多重複的定義,YAML 提供了由錨點標籤“&”和引用標籤“*”組成的語法,利用這套語法可以快速引用相同的一些數據...
# YAML
a: &anchor # 設置錨點
one: 1
two: 2
three: 3
b: *anchor # 引用錨點
// JSON
"a": {
"one": 1,
"two": 2,
"three": 3
},
"b": {
"one": 1,
"two": 2,
"three": 3
}
- 配合合併標籤“<<”使用可以與任意數據進行合併,你可以把這套操作想象成面嚮對象語言中的繼承...
# YAML
human: &base # 添加名爲 base 的錨點
body: 1
hair: 999
singer:
<<: *base # 引用 base 錨點,實例化時會自動展開
skill: sing # 添加額外的屬性
programer:
<<: *base # 引用 base 錨點,實例化時會自動展開
hair: 6 # 覆寫 base 中的屬性
skill: code # 添加額外的屬性
// JSON
"human": { "body": 1, "hair": 999 },
"singer": { "body": 1, "hair": 999, "skill": "sing" },
"programer": { "body": 1, "hair": 6, "skill": "code" }
相關資料
- YAML 官方網站https://yaml.org
- YAML 1.2 官方文檔https://yaml.org/spec/1.2/spec.html
- YAML - 維基百科https://zh.wikipedia.org/wiki/YAML
- YAML to JSON (格式在線轉換)https://www.json2yaml.com/convert-yaml-to-json
三、YAML用法
- stages
- cache
- only
- when
- before_script,script, after_script
- artifacts
- hidden_job && extends
- reserved keywords - include
stages:
stages是用來定義一個pipeline的,一個pipeline就像一個流水線,由一系列job來構成。比如在發佈(publish)之前要做lint,test,build,那麼這四個job就構成一個pipeline,寫成下面的樣子:
stages
- lint
- test
- build
- publish
然後你在gitlab的pipeline下面就能看到下面的圖:
上面我們雖然定義了一個pipeline,和4個job名稱,但是具體每個job做什麼還是不清楚的,接下來我們學習怎麼定義一個job。
job
以上面的lint爲例,我們需要執行npm run lint
命令來查看有沒有lint錯誤,那麼這個job可以寫成:
job-lint:
stage: lint
script: npm run lint
這裏job-lint
是任務名稱,script
是要在終端執行的命令,stage
表示這個job屬於哪個stage(pipeline的某個節點)。job名稱這裏要注意一點是,不能使用保留字,比如:不能把一個job的名字稱爲stages或者image,就像變量名不能用if一樣。相關文檔可以看這裏。
一個stage下可以有多個job,比如:打包的時候要打包到windows,mac,linux下,可以這樣:
job-build-mac:
stage: build
job-build-win;
stage: build
job-build-linux:
stage: build
有時候,我們希望一些任務是在某些場景下執行的,比如:打tag的時候再build,這時候可以使用only/except。
variables
在跑一個job的過程中,我們可能會需要環境變量,比如development,production,一個url,又或者是一個不希望顯示在控制檯的token,都可以作爲一個環境變量,設置一個環境變量通常有3種方式:在項目設置中,在yml文件中,或者通過api。
通過項目設置界面來配置環境變量的入口如下:
在variables下可以配置變量名和值,通常是需要保密的變量
當然我們也可以設置在yml中:
variables:
TEST: "HELLO WORLD"
然後就可以在腳本中引用:
script:
- echo $TEST
only/except
以上面的場景爲例,我們可以這樣寫job-build:
job-build:
stage: build
script: npm run build
only:
- tags
這樣,上面的job就只有在我們push tags時纔會觸發。如果我們希望一個job只在某一類分支有提交的時候觸發,可以這樣:
job-bugfix-build:
stage: build
script: npm run build
only:
- /^bugfix-.+$/
上面這個例子只有在bugfix爲前綴的分支產生提交的時候,纔會觸發job-bugfix-build。
然而,這樣並不足以讓這個任務跑起來,因爲CI是跑在docker裏面的,在執行run lint之前,我們需要把node環境搭起來,這就需要image保留字了:
image: node:12.18
添加了image之後,在任務開始之前,還要安裝依賴,我們使用before_script來完成這件事:
# 使用node鏡像
image: node:12.18
# 安裝依賴
before_script:
- npm install
有時候我們希望在某些場景下不執行某項任務,這時可以使用expect,比如不對hotfix進行lint:
job-lint-except-hotfix:
script:
- npm run lint
except: /^hotfix-.+$/
when
說了only,再說說when,when
是用來決定當前置任務失敗時,當前job是否執行,以及如何執行的問題。比如我們希望lint成功了再執行build:
build_job:
when: on_success
stage: build
needs: lint_job
再比如我們在執行發佈的時候,希望手動點擊發布按鈕來執行發佈:
publish_job:
when: manual
stage: publish
script: npm run deploy
artifacts
在前面提到的build job中,我們會使用webpack生成壓縮,混淆後的代碼,此時我們需要把它保存或者下載下來,這時就要用到artifacts了。用法如下:
build_job:
script: npm run build
artifacts:
name: "$CI_COMMIT_REF_NAME"
paths: dist/
artifacts最終會被打包成一個壓縮文件,這裏的path表示要添加到壓縮文件的文件或文件夾,name表示生成的壓縮文件的名字。然後在對應的任務詳情特面就可以下載:
include
正如通過程序通過模塊來實現代碼複用一樣,CI的yml配置可以通過include實現配置複用:
include:
- remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
- local: '/templates/.after-script-template.yml'
- template: Auto-DevOps.gitlab-ci.yml
這樣,我們可以把一些公用的環境變量或者job放到一個公共repo中,然後在其他項目中通過remote
來引用。
最後,github actions與gitlab ci在概念上很相似,不過配置的寫法不太一樣,官方還貼心的出了遷移文檔:migrating-from-gitlab-cicd-to-github-actions
四、實例:
variables:
DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://localhost:8888
.dind service: &dind_service
- alias: docker
name: ...
command:
- --insecure-registry=....
cache:
key: node-modules
paths:
- ./node_modules
stages:
- install
- build
- image
install dependency:
stage: install
image: ...
script:
- npm config set registry 倉庫地址
- sudo npm install
npm build:
stage: build
image: ...
script:
- sudo npm run build
- sudo takill node
artifacts:
name: "$CI_COMMIT_SHORT_SHA"
paths:
- build
build images:
build: image
image: ...
services: *dind_service
script:
- cp /opt/frontend/Dockerfile
- docker build ...
- docker push ...
dependencies:
- npm build
tags:
- docker
only:
refs:
- /^master/
- /^qa/
- /^dev/
variables:
- $CI_COMMIT_MESSAGE =~ /^build/
上面的YAML文件大致意思爲:一旦分支爲master 、 qa 、 dev開頭有提交信息爲build開頭的提交會觸發build這個job