V1.1语法说明文档
workflow,即流水线,是一连串具备一定串并联关系的任务组合,描述一连串的任务之间存在的依赖关系、输入输出参数,以及整个流水线的触发条件。
不同版本具备不同的语法规则,从零编写和学会workflow的声明方法请详阅对应版本的文档,避免无法正常解析。
版本信息
| 版本 | v1.1 |
|---|---|
| 维护者 | wanglin |
| 创建时间 | 2024-01-03 |
| 是否废弃 | 否 |
1. 语法特性
流水线通过YAML描述,描述文件的YAML语法规则基于YAML 1.2版本,书写时需要基于YAML 1.2支持的书写方式进行书写。后文中提及的语法特性是描述的基于此之上的解析规则,不涉及YAML 1.2语法的说明。
YAML(YAML Ain't Markup Language)是一种人类可读的数据序列化标准,它被广泛用于配置文件、数据交换语言、云计算等场景。YAML 1.2 是 YAML 的最新版本,于 2009 年发布。
相比于之前的版本,做了一些重要的改进和修正,包括:
- 更严格的类型转换规则,以避免一些常见的类型转换错误。
- 支持 JSON,即任何有效的 JSON 文件也是一个有效的 YAML 1.2 文件。
- 更好的 Unicode 支持。
YAML 1.2 的官方文档可以在以下链接找到:YAML 1.2 官方文档。这份文档详细地描述了 YAML 1.2 的所有特性和语法规则。
1.0. 编码风格
键命名风格
为使流水线描述文档风格统一,建议所有的键命名均采用"lower_case"的命名方式,尽量不使用大写字符,如下示例:
this_is_a_key: value jobs.this_is_a_job: job注意:命名风格不等于命名规则,如果不遵循建议的键命名风格并不会出现错误。
一级key声明风格
一级key的含义为整个YAML文档的第一级键,虽然第一级键无论以何种顺序排列不会影响解析结果,但基于统一风格的出发点考虑,建议用户按照如下顺序对一级key进行排列,且一级key之间通过一行空行间隔,如下示例:
# 版本声明,可以不存在,则默认使用v1.0语法解析 version: v1.1 # 流水线命名,必填 name: # 流水线触发设置,可以不存在 on: # 流水线变量,可以不存在 vars: # 流水线额外事件声明,可以不存在 events.xxx: # 流水线job声明,至少需要声明一个job jobs.xxx: # 流水线控制流说明 sequence: xxx:对于本版本流水线语法而言,一级key仅识别上述6类关键字,在这七种关键字之外的一级key将被忽略。如果某个关键字不存在,比如vars,剩余关键字建议仍保持上述先后顺序排列。
对于关键字的含义和详细语法说明见后文。
每行文本长度
为了保持良好的可读性,建议每行文本长度不要超过80个字符。这是一种常见的编程规范,可以使代码在大多数编辑器和终端中看起来更清晰。但这并不是强制性的规定,根据实际情况和个人习惯,可以适当调整。
对于长文本,可以利用YAML的特性转行声明,如下示例:
# 通过"|"语法保留换行符"\n" key: | this is a long long story, you could learn it step by step. # key = "this is a long long story,\nyou could learn it step by step." # 通过"|+"语法保留所有换行符"\n" key: |+ this is a long long story, you could learn it step by step. # key = "this is a long long story,\nyou could learn it step by step.\n\n\n" # 通过"|-"语法,去除末尾换行符"\n" key: |- this is a long long story, you could learn it step by step. # key = "this is a long long story,\nyou could learn it step by step." # 通过">"语法,虽然内容书写存在换行,但解析后的内容去除换行,以空格代替 key: > https://repo1/ https://repo2/ # key = "https://repo1/ https://repo2"更多的说明请参考YAML1.2官方文档(见 章节1. 语法特性 开头)
1.1. 基本声明
基本声明包含version与name两个一级key,前者用以锚定语法解析版本,后者用以标识流水线名称。
1.1.1. 语法版本声明
workflow支持多版本语法解析,对于不声明version的workflow而言,采用默认版本语法(v1.0)进行解析。 声明版本通过关键字version定义:
# 一般情况下,version会被声明在workflow.yaml的顶部
# 但version的位置并不会影响解析,确保version并非嵌套于其他key下即可
version: v1.1
根据声明的version的不同,请查阅不同版本的语法特性介绍。
1.1.2. 流水线命名
无论在什么版本,workflow的名字均由name字段定义。流水线的名字不要求唯一,可以是任意字符串。
name为一定需要定义的key,如果流水线yaml中缺少这个key解析器将不予通过。
version: v1.0
name: my workflow
1.2. 触发条件定义
1.2.1. workflow支持三种触发方式
- 手动触发: 基本的触发方式,不传递触发事件数据的方式,触发后将提交所有不存在依赖的任务
- 定时触发: 周期性自动触发方式,通过设定时间条件,系统自动触发。
- webhook触发: git仓库配置服务的webhook回调地址等信息,通过webhook回调请求自动触发。
定时触发和webhook触发方式通过关键字"on"定义,如果不需要这两种触发方式,on可以不声明。
示例:
version: v1.1
name: workflow
on:
# webhook触发事件定义
- type: webhook/pr
git_repo: https://atomgit.com/openeuler/radiaTest.git
branch: master
# 定时触发事件定义
- type: cron
crontab: 0 15 10 ? * MON-FRI
other_keys: other_values
1.2.2. webhook触发
webhook事件分为webhook/pr,webhook/push,webhook/note,webhook/issue四种类型。
PullRequest类事件
声明的事件键值对必须包含type(webhook/pr),git_repo(仓库地址),branch(仓库分支),action(场景,共8类)
即当对应的仓库分支存在PullRequest相关事件时,均会触发此流水线,包括新建PR/删除PR/合入PR/...等事件。
如果需要对PR事件进行更细致的筛选,用户可以指定action进行过滤。
Push类事件
声明的事件键值对必须包含type(webhook/push),git_repo(仓库地址),branch(仓库分支)
即当对应的仓库分支被推送更新后,均会触发此流水线。
评论类事件
声明的事件键值对必须包含type(webhook/note),git_repo(仓库地址),branch(仓库分支,仅支持PullRequest场景存在),notable_type(评论主体),notes(评论钩子)
即当对应的仓库分支的指定被评论主体(如PullRequest作为被评论主体)并且评论内容能够匹配评论钩子时,会触发此流水线。
Issue类事件
声明的事件键值对必须包含type(webhook/issue),git_repo(仓库地址),state(问题单状态)
即当对应的仓库分支存在Issue相关事件时,均会触发此流水线,包括新建issue/删除issue/...等事件。
state提供了对issue的场景细分能力,通过配置状态可以拆分不同场景。
webhook事件的声明支持矩阵式声明方式,从而帮助减少重复描述,如下所示:
on:
- type: webhook/note
git_repo: https://atomgit.com/openeuler/radiaTest.git
branch:
- master
- dev
- test
notable_type:
- PullRequest
notes:
- /retry
- /retest
这个例子意味着多个分支下的PullReques被评论了"/retry"或"/retest"都会触发此流水线
以上关于webhook讲解较为粗略,建议阅读更详细的webhook配置文档,以便更好的使用:
https://gitee.com/openeuler-customization/ods/blob/master/src/workflow_webhook/README.md
特别说明: 如果不仅仅需要触发,还需要在流水线中引用(该特性将在后文详述)触发事件中的字段,建议编辑者通过查阅不同git仓库的webhook文档了解。
Github: https://docs.github.com/webhooks
Gitlab: https://docs.gitlab.com/ee/user/project/integrations/webhooks.html
1.2.3. 定时触发事件
当配置的触发事件type字段为[cron,interval,date]值时,说明该事件为定时事件,对于某一个定时任务,type仅可为其中某一个取值,以下为简单示例:
on:
- type: cron
crontab: 0 15 10 ? * MON-FRI
- type: interval
seconds: 60
- type: date
run_date: 2024-01-01 00:00:00
- type: date
run_date: 164900500
以上配置,意味着时间满足任意一个场景时,流水线被执行。
关于三种类型的定时参数,参看以下官方文档配置即可:
https://apscheduler.readthedocs.io/en/stable/modules/triggers/cron.html
https://apscheduler.readthedocs.io/en/stable/modules/triggers/interval.html
https://apscheduler.readthedocs.io/en/stable/modules/triggers/date.html
1.3. 流水线全局变量定义
流水线的全局变量通过vars字段声明,当前版本支持字符串、数组、对象(字典/哈希表)三种格式。
version: v1.1
name: workflow
vars:
# 字符串
varA: string
# 数组
varB:
- elementA
- elementB
# 对象(支持多级结构嵌套)
varC:
keyA: valueA
# 嵌套数组
keyB:
- valueB1
- valueB2
# 嵌套对象
keyC:
keyC1:
流水线变量定义的目的在于定义整个流水线可以利用的若干变量(常量),定义后的变量可以被流水线任意阶段任务引用,从而避免每个任务对于该变量的重复冗余声明。
举例而言,假设某个流水线的10个任务均需要上述案例的varB变量作为Input(输入/入参),则只需要引用varB赋予给对应参数即可。
具体的引用方式详见后文对于引用语法的介绍。
如果一个流水线不需要定义任何流水线变量时,vars关键字可以不存在:
version: v1.1
name: workflow
other_keys: other_values
💡 注:v1.1相较上一个版本v1.0的新语法说明
除了在一级key中声明vars外,还可以在sequence中定义某些job特殊的vars,并且局部vars的优先级高于全局vars。
1.4. 额外事件声明
如果流水线内部的某个任务除了依赖于前置的任务外,还实际依赖于额外的webhook事件,或者依赖于一些额外的事件,则需要对这些额外的事件进行预声明。
用户通过events.xxx模式的key进行额外事件的声明,"xxx"为额外事件的命名。
**注意:**大多数情况下,用户不需要定义额外事件。额外事件不支持定时以及手动事件声明。
1.4.1. webhook事件
当定义额外webhook事件时,该事件需求定义的key-values与上文流水线触发设置中介绍的一致,如下示例:
version: v1.1
name: workflow
vars:
events.eventA:
type: webhook/pr
git_repo: https://gitee.com/openeuler/repositry.git
branch:
- master
- dev
1.4.2. job事件
除了webhook事件外,额外事件可以定义一种新的事件类型,即job类型事件。job类型事件分为stage和step两个子类,如下示例:
version: v1.1
name: workflow
vars:
# 额外事件A - jobA进入boot阶段
events.eventA:
type: job/stage
job: jobA
job_stage: boot
# 额外事件B - jobA进入名为testcase001的步骤
events.eventB:
type: job/step
job: jobA
job_step: testcase001
# 额外事件C - jobA进入finish阶段且incomplete
events.eventC:
type: job/stage
job: jobA
job_stage: finish
job_health: incomplete
job类型的事件除了type和job必填外,其他字段可以根据需求从job类型事件的全集keys中选取(job_stage/job_health/job_step/nickname)。
定义后的job类型事件如何使用见后文任务定义和控制流声明章节。
1.5. 任务定义
1.5.1. 基础概念
对于所有流水线涉及的任务,都需要通过jobs.xxx模式的一级key进行一次声明,主要目的为定义任务的Input(输入/入参),且每个被声明job的value均要求为对象(字典/哈希表)格式(或者为空)。
jobs.xxx类关键字常见的两种二级关键字为defaults和overrides,这两个二级key可以不声明,但如果具备value,则value必须为对象(字典/哈希表)格式,用以声明待提交的job即为模板所具备的所有参数。
如下所示:
name: workflow
vars:
# 空value的job缺省声明
jobs.jobA:
# 空defaults value, 空overrides value声明
jobs.jobB:
defaults:
overrides:
# 缺省defaults,overrides非空声明
jobs.jobB:
overrides:
# 字符串
keyA: valueA
# 数组
keyB:
- valueB1
- valueB2
# 对象(支持多级嵌套)
keyC:
keyC1: valueC1
keyC2:
- valueC2
除了嵌套的声明方式,流水线语法支持扁平化的方式减少声明的难度,如下示例:
jobs.jobB:
overrides:
keyC.keyC1: valueC1
# 等价于
jobs.jobB:
overrides:
keyC:
keyC1: valueC1
**注意:**这种等价仅于defaults和overrides下有效。
defaults和overrides意义如字面含义所示,defaults中定义的key-values如果原job中存在对应key,则以原job中的value为实际提交value;overrides中定义的key-values将无条件覆盖到原job的值提交。
对于job的概念,以及原job.yaml的内容,建议查阅compass-ci/lkp-tests的文档进行了解:
- 如何向compass-ci/lkp-tests新增job: https://gitee.com/compass-ci/lkp-tests/blob/master/doc/add-testcase.zh.md
- job的定义: https://gitee.com/compass-ci/lkp-tests/blob/master/jobs/README.md
- job示例: https://gitee.com/compass-ci/lkp-tests/blob/master/programs/ltp/jobs/ltp-bm.yaml
💡 注:v1.1相较上一个版本v1.0的新语法说明
在v1.1版语法中,允许jobs.XX方式定义job,在sequence控制流中并不全部引用,并不会解析报错。
1.5.2. 任务别名定义
通常情况下,jobs.xxx模式中xxx即为被声明的job名,如如果计划声明一个ltp-bm的任务,则声明jobs.ltp-bm。但在某些流水线中,可能同一个任务需要运行多次,且任务实际的入参并不相同,因此设计多次声明的可能。在这种情况下,则需要利用"别名"语法特性。
别名的声明方式如下示例:
jobs.ltp-bm:first-ltp-bm:
jobs.ltp-bm:second-ltp-bm:
这两个被声明的任务实际指向的都是ltp-bm这同一个job,但是因为别名所以流水线会将其看作两个不同的个体。
1.5.3. 额外事件依赖声明
除了defaults和overrides两种常用的二级keys外,jobs.xxx还支持声明depends字段,本字段用以声明额外依赖(额外事件,即上文章节1.3内容的应用)
events.eventA:
xxx: xxx
events.eventC:
xxx: xxx
jobs.jobA:
defaults:
default_keyA: valueA
overrides:
override_keyB: valueB
depends:
# 额外依赖于上文中通过events.eventA声明的事件
# 只需要写key:的形式,不需要填value,填了也会被忽略
eventA:
# 且额外依赖于上文中通过events.eventC声明的事件
eventC:
对于jobA而言,depends字段相当于定义了若干AND逻辑关系的额外依赖,当且仅当所有依赖的事件均发生后jobA才会被提交执行。
当depends不声明的时候,jobA的依赖仅取决于其处于控制流的位置(详见后文控制流声明),否则为控制流依赖于额外依赖的逻辑与结果。
submit_jobA = [jobA's depends parsed from controlflow] AND [jobA's depends defined from 'depends']
1.6. 控制流声明
1.6.1. 基础特性
流水线任务的串并行结构通过控制流声明对已通过一级key”jobs.xxx“预声明的各个任务进行编排,通过一级key“sequence”定义,如下所示:
version: v1.1
name: workflow
# jobA:first进入boot阶段
events.eventA:
type: job/stage
job: jobA
nickname: first
job_stage: boot
# 定义别名为first的jobA
jobs.jobA:first:
overrides:
jobs.jobB:
overrides:
# 额外依赖于"jobA:first进入boot阶段"事件
depends:
eventA:
# 控制流声明
sequence:
# 声明并行子结构
parallel:
# jobA:first和jobB并行
jobA:first:
jobB:
控制流声明中,存在sequence、parallel、matrix、vars四种关键字,除了关键字外,所有key都会被认作对已预声明的job的引用。对于所有job的引用,需要确保引用的job全称(包含别名)在流水线一级key中存在(以jobs.xxx预声明)。如果sequence中引用了jobA,但流水线一级key中缺少jobs.jobA这个key,解析器将不会给予通过。
**注意:**控制流声明的根(一级key)必须为sequence。
关键字sequence
sequence意在声明一个串行结构,在sequence下的所有key将被解析为按声明顺序(从上到下)排列的一连串成员,每一个成员必然依赖于其上面一个的成员。
sequence: |---------| |---------| jobA: = | jobA | =========> | jobB | jobB: |---------| |---------|sequence和job一样,可以通过sequence:xxx:的方式定义别名,该别名仅在一级串行子结构中存在实际意义,非一级子结构的别名仅起标识作用,具体参考下文stage声明说明。
# 根sequence,不可添加别名,为控制流声明关键字 sequence: # 一级串行子结构,别名为seqA sequence:seqA: # 二级串行子结构,别名为seqB sequence:seqB:如上所示,sequence的key并不一定只能是job,当sequence内部的key同样是sequence时,意味着串行结构的嵌套。当然,对于纯sequence的嵌套是不具备实际意义的,仅为分组标识,单纯的串行嵌套相当于没有嵌套。
sequence: < - - - - - seqA - - - - - - > sequence:seqA: |---------| |---------| |---------| jobA: = | jobA | =========> | jobB | =========> | jobC | jobB: |---------| |---------| |---------| jobC:关键字parallel
parallel意在声明一个并行结构,在parallel下的所有key将被解析为并列的若干成员,每一个成员都依赖于整个parallel都前置依赖,互相之间不存在控制流定义的依赖关系(可以存在通过depends额外声明的依赖,额外跳线依赖不被控制流声明控制)
___________|___________ sequence: | | parallel: |---------| |---------| jobA: = | jobA | | jobB | jobB: |---------| |---------| |______________________| |注意,parallel一定不可以声明在workflow.yaml的一级key,对于控制流声明而言,根key一定是sequence。
和sequence一致,parallel也可以以parallel:xxx:的方式定义别名,该别名同样仅在一级并行子结构中存在实际含义,非一级子结构的别名仅起标识作用,具体参考下文stage声明说明。
同理,单纯的并行嵌套相当于没有嵌套,如下示例,等价于jobA、jobB、jobC三者并行。
___ _________________|_______________ sequence: | | _________|__________ ___ parallel:prlA: | |---------| |----|----| |----|----| | jobA: = prlA | jobA | | jobB | | jobC | prlB parallel:prlB: | |---------| |----|----| |----|----| | jobB: | | |__________________| _|_ jobC: _|_ |_______________________________| |关键字matrix
matrix关键字将在1.7章节中详细说明。
💡 注:vars关键字为v1.1相较上一个版本v1.0的新语法
关键字vars
sequence中声明的vars和一级key中vars的含义和用法完全相同,sequence中的vars存在局部作用域,并且局部作用域的优先级高于全局作用域的优先级,即声明位置越近优先级越高,常用于某些job要引用的变量与全局变量的值不同,需要覆盖全局定义变量的场景。另外,vars可以定义在任意sequence或parallel结构中。
优先级的说明如下示例:
# 全局定义的变量 vars: keyA: valueA jobs.job1: overrides: key1: ${{ vars.keyA }} jobs.job2: overrides: key1: ${{ vars.keyA }} jobs.job3: overrides: key1: ${{ vars.keyA }} sequence: job1: sequence:s1: # 此处定义的vars只对sequence:s1结构生效 vars: keyA: valueB job2: parallel:p1: # 此处定义的vars只对parallel:p1结构生效 vars: keyA: valueC job3: # 各job变量引用的实际值: # job1.key1 = valueA # job2.key1 = valueB # job3.key1 = valueC
1.6.2. job的补充声明
由上文可知,job无论是defaults、overrides还是depends的声明,可以声明在jobs.xxx这个key之下,这也是比较推荐的用法。但其实在控制流声明中,用户可以对job进行补充声明,补充的声明将深层update到预定义的job声明中,如下所示:
name: workflow
jobs.jobA:
defaults:
keyA: valueA
overrides:
keyB:
keyB1: valueB1
keyB2:
keyB21: valueB21
sequence:
jobA:
overrides:
# 与上文中jobs下的overrides特性相同
# 采用keyB.keyB2.keyB21和keyB.keyB2.keyB22为key,即
# keyB.keyB2.keyB21: valueB21_new
# keyB.keyB2.keyB22: valueB22
# 与下述声明方式等价
keyB:
keyB2:
keyB21: valueB21_new
keyB22: valueB22
在这个例子中,sequence中将jobA的预定义的overrides下的keyB21重新定义为valueB2_new,且在keyB2下新创建了一个keyB22的键值对。
补充声明特性以对象(字典/哈希表)的递归update实现,一定为控制流中的定义覆写jobs的预定义。
💡 注:以下为v1.1相较上一个版本v1.0的新语法
在sequence中除了可以补充定义job的overrides和defaults字段外,还支持定义always、if、unless执行条件语法。
always关键字
always用于决定job是否一定会被提交。在常规控制流的依赖关系中,如果前置job执行失败,后面依赖它的job将会阻塞不会再被提交,如果在job中声明了always: true,前置job运行失败或者异常,后面的任务都会被提交。
sequence: jobA: jobB: # jobB运行结束,运行结果成功、失败或是异常,jobC都会被提交运行 jobC: always: truealways关键字有一种语法糖的写法:jobX!,如下示例的写法和上面yaml作用相同:
sequence: jobA: jobB: # jobB运行结束,运行结果成功、失败或是异常,jobC都会被提交运行 jobC!:
另外,如果always关键字没有声明,缺省值取false。
if/unless关键字
if和unless关键字用于控制job是否需要被提交,如果if条件判断的结果为true,job才会被提交,否则此job将被跳过,并将job_stage设置为finish,job_health设置为skipped,unless的判断逻辑和if正好相反。
sequence: jobA: jobB: if: ${{ jobs.jobA.result.id }} # 如果jobA的result.id有值,jobB会被提交,否则jobB不会被运行 jobC:如果job中没有声明关键字if,缺省值取true;如果job中always和if关键字同时存在,优先判断if关键字的执行逻辑。
1.7. 流水线阶段(stage)声明
在章节1.6. 控制流声明中有提及,无论是sequence还是parallel分别可以通过sequence:xxx:和parallel:xxx:的形式声明别名。流水线web服务将基于下述规则划分控制流的不同阶段,规则如下所示:
1. 当且仅当sequence和parallel为根sequence下的一级结构时,其别名等同于阶段名。
2. 当根sequence下存在job名时(非sequence也非parallel),该job以自身job的别名作为阶段名(若无别名则以job名)独立被识别为一个阶段。
3. 阶段存在向后包裹的特点,直到下一个有效阶段声明前,所有结构属于同一个阶段。
3. 沿着根sequence向下检索,在遇到第一个有效的stage命名之前,所有的结构均属于“未命名”阶段。
sequence:
阶段(stage)
parallel: ——|
job0: > 未命名
job1: ——|
job2:build-job: —— > build-job
jobA: ——|
sequence: > jobA
jobB: ——|
parallel:prlA: ——|
jobC: |
jobD: |
sequence:seqB: > prlA
jobE: |
jobF: ——|
sequence:seqC: —— > seqC
jobG: ——|
p.s. 阶段仅会影响web端的渲染,控制流的实际意义不依赖于阶段的定义,换而言之,如果不考虑可视化的便利性,可以不对阶段命名深究。
1.8. Matrix语法特性
1.8.1. 基本概念
用户可以在控制流串行结构的任意位置可以插入一个matrix关键字,用以混入(Mixin)局部的矩阵(参数组合),从而改变后续任务的上下文(Context)。
matrix关键字同样可以声明别名,用以避免对象(字典/哈希表)的重key异常,但除了区别外没有实际意义。
注意: matrix不能直接声明在parallel关键字下,只能声明在sequence关键字下。
matrix的声明结构一定为如下格式:
sequence:
matrix:
paramA:
- valueA1
- valueA2
- valueAn
paramB:
- valueB1
- valueB2
即,matrix是一个对象(字典/哈希表),且所有一级value均为数组(列表)。
上述例子中matrix的含义为,对所处位置的流水线上下文混入矩阵,其中paramA有三种可能的取值,paramB有两种可能的取值,即共3*2共6种取值组合。
matrix: _
paramA: | 1. paramA = valueA1; paramB = valueB1
- valueA1 | 2. paramA = valueA1; paramB = valueB2
- valueA2 | 3. paramA = valueA2; paramB = valueB1
- valueAn => { 4. paramA = valueA2; paramB = valueB2
paramB: | 5. paramA = valueAn; paramB = valueB1
- valueB1 |_ 6. paramA = valueAn; paramB = valueB2
- valueB2
当流水线上下文混入(Mixin)一个局部的矩阵后,流水线的上下文将会根据参数取值组合的种数裂解成多个“分支”,每一个“分支”的上下文依据其中一种取值组合。当这个分支之后的任务直接引用上下文中的paramA时,会根据当前上下文的paramA取值,后续任务的驱动也会与其他”分支“独立。
举例而言:
sequence:
jobA:
parallel:
jobB:
sequence:
matrix:
arch:
- aarch64
- x86_64
jobC:
jobD:
根据 章节1.5. 控制流声明 的介绍,不难看出,这个描述声明的结构如下:
________
| |
|-------| jobB |----------------------------|
| |________| |
________ | | ________
| | | | | |
-------| jobA |-------| |-------| jobD |-------->
|________| | | |________|
| ________ |
| / \ | | |
|-------| matrix |-------| jobC |---------|
\ / |________|
1. arch = aarch64;
2. arch = x86_64
对于jobA和jobB,如果他们在被提交的时候引用“当前上下文”(所谓当前为被提交的时间点)中的arch变量,他们将取不到任何值。
p.s. 关于引用的概念详见章节1.6.
而对于jobC和jobD而言,他们实际上被裂解到了并行的两个“分支”上,其中一个分支上下文中的arch是aarch64而另一个分支上的arch是x86_64,即上述控制流结构等价于:
1. arch = aarch64;
________
| |
|----------| jobB |---------|
| |________| |
________ | | ________
| | | | | |
-------| jobA |-------| |-------| jobD |-------->
|________| | | |________|
| ________ | aarch64
| | | |
|----------| jobC |---------|
|________|
aarch64
2. arch = x86_64
________
| |
|----------| jobB |---------|
| |________| |
________ | | ________
| | | | | |
-------| jobA |-------| |-------| jobD |-------->
|________| | | |________|
| ________ | x86_64
| | | |
|----------| jobC |---------|
|________|
x86_64
这两个矩阵参数组合“分支”共享jobA和jobB的前置依赖,但aarch64的jobD只会依赖于aarch64的jobC,即各分支依赖独立。
这样避免了在很多场景下的相同结构的重复声明。
1.8.2. 矩阵x矩阵
流水线控制流支持多matrix在不同位置声明,在这种情况下,下文矩阵受到上文矩阵影响,下文矩阵实际为上下文矩阵相乘的结果,如下示例:
sequence:
matrix:m1:
os:
- openeuler
os_version:
- 20.03
- 22.03-LTS
jobA:
matrix:m2:
arch:
- aarch64
- x86_64
jobB:
对于这个例子而言,jobA共有两种上下文分支,而jobB共有4种,如下所示:
________ ________
| | | |
--------------| jobA |-----------------------------------------| jobB |----------------------->
|________| |________|
1. os=openeuler; os_version=20.03 1. os=openeuler;os_version=20.03;arch=aarch64;
2. os=openeuler; os_version=22.03-LTS 2. os=openeuler;os_version=20.03;arch=x86_64;
3. os=openeuler;os_version=22.03-LTS;arch=aarch64;
4. os=openeuler;os_version=22.03-LTS;x86_64;
因此对于声明此例控制流的关于jobA和jobB的流水线,实际jobA将会被提交两次,jobB将会被提交4次,jobB的1和2分支依赖于jobA的1分支,jobB的3和4分支依赖于jobA的2分支,jobB的最终参数组合即jobA之前声明的matrix与jobB之前的matrix相乘的结果。
1.8.3. excludes语法特性
matrix支持通过excludes声明排除特定的组合,如下所示:
sequence:
matrix:
os:
- openeuler
- centos
os_version:
- "20.03"
- 7
excludes:
# 下述两种描述形式均可支持
- {"os": "openeuler", "os_version": "7"}
- os: centos
os_version: "20.03"
此声明方式意为此矩阵只存在两种参数组合,即 ”os=openeuler;os_version=20.03“ 和 “os=centos;os_version=7”。
1.8.4. 参数组合语法糖
同时,matrix具备一种简化excludes声明的语法糖"|",以上述样例可以改写为:
sequence:
matrix:
os|os_version:
- openeuler | 20.03 #有无空格或者制表符均支持
- centos | 7 #推荐以制表符分隔,这样的声明较为直观
1.8. "引用"表达式声明
1.8.1. 基本概念
对于一条正在运作的流水线而言,其上下文是动态的,每执行完成一个任务,每感知到一个有效事件,“当前”上下文都会发生变化。
流水线运行上下文(Context)由六个固定的namespace组成:
- vars,流水线变量空间(流水线静态变量全集)
- event,事件空间(事件数据全集)
- jobs,任务空间(前置已完成的任务数据)
- matrix,矩阵空间(当前矩阵参数组合分支的参数集合)
- depends,未满足的依赖事件清单(此namespace一般不会被引用)
- fullfilled,已履行的依赖事件清单(此namespace一般不会被引用)
流水线不仅仅支持对定量的声明,流水线具备“引用”的语法特性,可以对“当前上下文”的变量进行引用,以及进行字符串拼接和python表达式运算。
“引用”由模式 ${{ xxxx }} 识别,通过"."的方式获取不同namespace下的所有value,支持下述两种使用方式:
字符串拼接引用
# 取vars空间中的varA变量的值,并且与vars空间中的varB变量的值,最后通过"-"拼接 key: ${{ vars.varA }}-${{ vars.varB }} # 取当前矩阵参数组合的os、os_version、arch拼接命名 project_name: my_project:${{ matrix.os }}:${{ matrix.os_version }}:${{ matrix.arch }}对于这种拼接引用的方式,需要用户确保引用变量的值一定是字符串。如果实际的值不为字符串或者无法转换为字符串,那么采用这种引用声明的job很可能无法正常提交。
单引用
单引用的情况下,引用表达式的结果可以为字符串、数字、数组(列表)或者哈希表(字典),不受类型影响。
# 取vars空间中的数组arrayA,作为key的值 key: ${{ vars.arrayA }} # 取前置已完成的jobA的输出result.arrayB,作为key的值 key: ${{ jobs.jobA.result.arrayB }}
**注意:**引用特性仅支持在defaults和overrides下使用,即jobs.xxx下的defaults和overrides或者sequence下某个job的defaults和overrides。后续演进的语法版本中将加入"在matrix中引用vars变量"的支持。
1.8.2. python语法支持
对于任意引用内部而言,在引用的变量被实际的值替换后,替换后的内容将会被当作python表达式运行,如下示例:
# 取多个不同namespace的变量进行数值运算
key: ${{ vars.numA + jobs.jobA.result.success_num }}
# 调用python datetime模块,获取年月日并拼接字符串
project_name: ${{ vars.my_name }}-${{ datetime.datetime.now().year }}-{{ datetime.datetime.now().month }}-${{ datetime.datetime.now().day }}
# 调用字符串处理方法,对字符串进行大小写转换,split等操作
key: ${{ vars.stringA.lower() }}
key: ${{ vars.stringA.split(':') }}
# 单纯通过python表达式计算数值,不对变量进行引用,如计算一天一共有多少秒
key: ${{ 24*60*60 }}
支持的非内置Python模块:
| 模块名 | 作用 | 官方文档链接 |
|---|---|---|
| re | 提供正则表达式匹配操作 | https://docs.python.org/3/library/re.html |
| math | 提供数学运算函数 | https://docs.python.org/3/library/math.html |
| time | 提供时间相关函数 | https://docs.python.org/3/library/time.html |
| datetime | 提供日期和时间处理函数 | https://docs.python.org/3/library/datetime.html |
支持的安全内置Python模块:
| 类型 | 模块名 |
|---|---|
| 数据类型 | object, bool, int, float, complex, str, bytes, bytearray, tuple, list, set, frozenset, dict |
| 数学运算 | abs, round, pow, divmod |
| 迭代器 | iter, next |
| 集合操作 | len, sum, min, max, all, any, map, filter, zip, enumerate, sorted, reversed |
| 数字转换 | bin, hex, oct |
| 字符串格式化 | ascii, repr, chr, ord, format |
| 变量和内存 | dir, locals, globals, id, hash |
| 类型检查 | isinstance, issubclass, callable |
2. workflow.yaml完整示例
以下是一个完整的workflow.yaml文件示例:
# 语法版本声明
version: v1.0
# 流水线命名
name: 每日构建
# 触发设置
on:
# 设定定时触发事件,每天00:00触发
- type: cron
week_day:
- Monday
- Tuesday
- Wednesday
- Thursday
- Friday
- Saturday
- Sunday
time: "00:00"
start_date: "2023-10-18"
# 流水线变量设置
vars:
eulermaker_account: account
eulermaker_password: passwd
os: os
os_version: version
# 任务声明
jobs.eulermaker-build-project:everything:
overrides:
project_name: ${{ vars.os }}-${{ vars.os_version }}:everything
build_type: full
build_arch: ${{ matrix.arch }}
secrets:
ACCOUNT: ${{ vars.eulermaker_account }}
PASSWORD: ${{ vars.eulermaker_password }}
testbox: vm-2p8g
jobs.eulermaker-build-project:epol:
overrides:
project_name: ${{ vars.os }}-${{ vars.os_version }}:epol
build_type: full
build_arch: ${{ matrix.arch }}
secrets:
ACCOUNT: ${{ vars.eulermaker_account }}
PASSWORD: ${{ vars.eulermaker_password }}
testbox: vm-2p8g
jobs.eulermaker-create-image:
overrides:
image_project_params:
pipeline_info:
pipeline_name: ${{ vars.os }}-${{ vars.os_version }}-${{ datetime.datetime.now().year }}-${{ datetime.datetime.now().month }}-${{ datetime.datetime.now().day }}-1
group: dailybuild
category: standard
scene: cloud
image_format: qcow2
arch: ${{ matrix.arch }}
image_config:
release_image_config:
repo_url: >
http://xxxxx/
http://xxxx/
http://xxx/
http://xxxxxx/
product: ${{ vars.os.lower() }}
version: ${{ vars.os_version }}-${{ datetime.datetime.now().year }}-${{ datetime.datetime.now().month }}-${{ datetime.datetime.now().day }}
secrets:
ACCOUNT: ${{ vars.eulermaker_account }}
PASSWORD: ${{ vars.eulermaker_password }}
testbox: vm-2p8g
jobs.eulermaker-build-image:
overrides:
secrets:
ACCOUNT: ${{ vars.eulermaker_account }}
PASSWORD: ${{ vars.eulermaker_password }}
pipeline_id: ${{ jobs.eulermaker-create-image.result.id }}
testbox: vm-2p8g
runtime: ${{ 24*60*60 }}
jobs.qcow2rootfs:
overrides:
qcow2rootfs.qcow2_os: ${{ jobs.eulermaker-build-image.result.product }}
qcow2rootfs.qcow2_arch: ${{ matrix.arch }}
qcow2rootfs.qcow2_version: ${{ jobs.eulermaker-build-image.result.version }}
qcow2rootfs.qcow2_url: ${{ jobs.eulermaker-build-image.result.image_link }}
qcow2rootfs.rootfs_protocol: nfs
qcow2rootfs.rootfs_server: "172.168.131.2"
qcow2rootfs.rootfs_path: os-rw
testbox: vm-2p32g
jobs.mugen-smoke-baseinfo:
overrides:
os: ${{ jobs.qcow2rootfs.result.os }}
os_version: ${{ jobs.qcow2rootfs.result.version }}
os_mount: nfs
arch: ${{ jobs.qcow2rootfs.result.arch }}
testbox: vm-2p8g
jobs.mugen-smoke-basic-os:
overrides:
os: ${{ jobs.qcow2rootfs.result.os }}
os_version: ${{ jobs.qcow2rootfs.result.version }}
os_mount: nfs
arch: ${{ jobs.qcow2rootfs.result.arch }}
testbox: vm-2p8g
# 控制流声明
sequence:
# 矩阵声明
matrix:
arch:
- aarch64
- x86_64
# 并行子结构声明
parallel:build:
eulermaker-build-project:everything:
eulermaker-build-project:epol:
# 串行子结构声明
sequence:create-image:
eulermaker-create-image:
eulermaker-build-image:
qcow2rootfs:
parallel:AT:
mugen-smoke-baseinfo:
mugen-smoke-basic-os:
3. v1.1新语法
3.1 sequence局部变量定义
控制流中新增对vars关键字的支持。sequence中声明的vars和一级key中vars的含义和用法完全相同,sequence中的vars存在局部作用域,并且局部作用域的优先级高于全局作用域的优先级,即声明位置越近优先级越高,常用于某些job要引用的变量与全局变量的值不同,需要覆盖全局定义变量的场景。另外,vars可以定义在任意sequence或parallel结构中。
优先级的说明如下示例:
# 全局定义的变量
vars:
keyA: valueA
jobs.job1:
overrides:
key1: ${{ vars.keyA }}
jobs.job2:
overrides:
key1: ${{ vars.keyA }}
jobs.job3:
overrides:
key1: ${{ vars.keyA }}
sequence:
job1:
sequence:s1:
# 此处定义的vars只对sequence:s1结构生效
vars:
keyA: valueB
job2:
parallel:p1:
# 此处定义的vars只对parallel:p1结构生效
vars:
keyA: valueC
job3:
# 各job变量引用的实际值:
# job1.key1 = valueA
# job2.key1 = valueB
# job3.key1 = valueC
3.2 判断job执行条件
在sequence中除了可以补充定义job的overrides和defaults字段外,还支持定义always、if、unless执行条件语法。
always关键字
always用于决定job是否一定会被提交。在常规控制流的依赖关系中,如果前置job执行失败,后面依赖它的job将会阻塞不会再被提交,如果在job中声明了always: true,前置job运行失败或者异常,后面的任务都会被提交。
sequence: jobA: jobB: # jobB运行结束,运行结果成功、失败或是异常,jobC都会被提交运行 jobC: always: truealways关键字有一种语法糖的写法:jobX!,如下示例的写法和上面yaml作用相同:
sequence: jobA: jobB: # jobB运行结束,运行结果成功、失败或是异常,jobC都会被提交运行 jobC!:
另外,如果always关键字没有声明,缺省值取false。
if/unless关键字
if和unless关键字用于控制job是否需要被提交,如果if条件判断的结果为true,job才会被提交,否则此job将被跳过,并将job_stage设置为finish,job_health设置为skipped,unless的判断逻辑和if正好相反。
sequence: jobA: jobB: if: ${{ jobs.jobA.result.id }} # 如果jobA的result.id有值,jobB会被提交,否则jobB不会被运行 jobC:如果job中没有声明关键字if,缺省值取true;如果job中always和if关键字同时存在,优先判断if关键字的执行逻辑。
3.3 job声明不选用
通过jobs.jobX方式声明的job不一定全部在sequence中选用。
jobs.jobA:
jobs.jobB:
sequence:
jobB:




