长期支持版本

    社区创新版本

      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文档了解。

      1. Gitee: https://help.gitee.com/enterprise/code-manage/%E9%9B%86%E6%88%90%E4%B8%8E%E7%94%9F%E6%80%81/WebHook/WebHook%20%E7%AE%80%E4%BB%8B

      2. Github: https://docs.github.com/webhooks

      3. 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的文档进行了解:

      1. 如何向compass-ci/lkp-tests新增job: https://gitee.com/compass-ci/lkp-tests/blob/master/doc/add-testcase.zh.md
      2. job的定义: https://gitee.com/compass-ci/lkp-tests/blob/master/jobs/README.md
      3. 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: true
        

        always关键字有一种语法糖的写法: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: true
        

        always关键字有一种语法糖的写法: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:
      

      文档捉虫

      “有虫”文档片段

      问题描述

      提交类型 issue

      有点复杂...

      找人问问吧。

      PR

      小问题,全程线上修改...

      一键搞定!

      问题类型
      规范和低错类

      ● 错别字或拼写错误;标点符号使用错误;

      ● 链接错误、空单元格、格式错误;

      ● 英文中包含中文字符;

      ● 界面和描述不一致,但不影响操作;

      ● 表述不通顺,但不影响理解;

      ● 版本号不匹配:如软件包名称、界面版本号;

      易用性

      ● 关键步骤错误或缺失,无法指导用户完成任务;

      ● 缺少必要的前提条件、注意事项等;

      ● 图形、表格、文字等晦涩难懂;

      ● 逻辑不清晰,该分类、分项、分步骤的没有给出;

      正确性

      ● 技术原理、功能、规格等描述和软件不一致,存在错误;

      ● 原理图、架构图等存在错误;

      ● 命令、命令参数等错误;

      ● 代码片段错误;

      ● 命令无法完成对应功能;

      ● 界面错误,无法指导操作;

      风险提示

      ● 对重要数据或系统存在风险的操作,缺少安全提示;

      内容合规

      ● 违反法律法规,涉及政治、领土主权等敏感词;

      ● 内容侵权;

      您对文档的总体满意度

      非常不满意
      非常满意
      提交
      根据您的反馈,会自动生成issue模板。您只需点击按钮,创建issue即可。
      文档捉虫
      编组 3备份