---
url: /zh/build/grammar.md
---
CNB 流水线通过 `.cnb.yml` 配置文件来定义自动化构建流程。

## 基本结构

CNB 流水线采用层级结构，从外到内依次是：

| 层级 | 说明 | 示例 |
|------|------|------|
| 触发分支 | 指定哪些分支的代码会触发流水线 | `main`、`dev` |
| 触发事件 | 指定什么操作会触发流水线 | `push`、`pull_request` |
| Pipeline（流水线） | 一次完整的构建流程，包含多个阶段 | 构建流程 |
| Stage（阶段） | 流水线中的一个步骤，可包含一个或多个任务 | 安装依赖 |
| Job（任务） | 最小的执行单元，执行具体的命令或插件 | 执行命令 |

### 执行流程示意

```text
触发分支 (main)
  └─ 触发事件 (push)
      ├─ Pipeline (流水线)
      │   └─ Stage（阶段）
      │       └─ Job（任务）
      └─ Pipeline (流水线)
          └─ Stage（阶段）
              └─ Job（任务）
```

### 完整示例

以下是一个包含所有层级的完整示例：

```yaml title=".cnb.yml"
main: # 触发分支：在 main 分支上触发
  push: # 触发事件：代码推送时触发
    - name: pipeline-1 # 流水线名称
      stages:
        - name: stage-1 # 阶段名称
          jobs:
            - name: job-1 # 任务名称
              script: echo "in pipeline-1"
    - name: pipeline-2 # 流水线名称
      stages:
        - name: stage-1 # 阶段名称
          jobs:
            - name: job-1 # 任务名称
              script: echo "in pipeline-2"
```

也支持通过对象的形式声明多个流水线：

```yaml title=".cnb.yml"
main: # 触发分支
  push: # 触发事件
    pipeline-1: # 流水线标识
      stages:
        - name: stage-1
          jobs:
            - name: job-1
              script: echo "Hello World"
    pipeline-2: # 流水线标识
      stages:
        - name: stage-1
          jobs:
            - name: job-1
              script: echo "Hello World"
```

## 触发分支 {#trigger-branch}

**作用**：指定哪些分支的代码会触发流水线。

**类型**：字符串

### 支持模式

| 模式类型 | 说明 | 示例 |
|---------|------|------|
| 精确匹配 | 精确匹配分支名 | `main`、`dev`、`release` |
| 通配符匹配 | 使用 glob 语法匹配 | `feature/*`、`hotfix/*` |
| 兜底匹配 | 匹配所有未明确指定的分支 | `$` |

#### 分支匹配示例

```yaml title=".cnb.yml"
# 只在 main 分支触发
main:
  push:
    - stages:
        - echo "main branch"

# 匹配所有 feature 开头的分支
feature/*:
  push:
    - stages:
        - echo "feature branch"

# 兜底：匹配其他所有分支
$:
  push:
    - stages:
        - echo "other branches"
```

::: tip 更多详情
参考 [触发机制#触发分支](./trigger-rule.md#trigger-branch)
:::

## 触发事件 {#trigger-event}

**作用**：指定什么操作会触发流水线执行。

### 常用事件

| 事件名 | 触发时机 |
|--------|---------|
| push | 代码推送到分支时触发 |
| pull\_request | 创建或更新 Pull Request 时触发 |
| tag\_push | 推送标签时触发 |
| branch.delete | 删除分支时触发 |

#### 触发事件示例

```yaml title=".cnb.yml"
main:
  # 代码推送时执行测试
  push:
    - stages:
        - npm test

  # PR 时执行代码检查
  pull_request:
    - stages:
        - npm run lint
```

::: tip 更多详情
参考 [触发机制#触发事件](./trigger-rule.md#trigger-event)
:::

## Pipeline {#pipeline}

`Pipeline` 表示一个流水线，包含一个或多个阶段 `Stage`，每个 `Stage` 依次执行。

### 配置项概览

Pipeline 支持以下配置项：

| 配置项 | 类型 | 说明 |
|--------|------|------|
| [name](#pipeline-name) | `String` | 流水线名称 |
| [runner](#pipeline-runner) | `Object` | 构建节点配置（tags、cpus） |
| [docker](#pipeline-docker) | `Object` | Docker 环境配置（image、build、devcontainer、volumes） |
| [git](#pipeline-git) | `Object` | Git 仓库配置（enable、submodules、lfs） |
| [services](#pipeline-services) | `Array` | 构建服务（docker、vscode） |
| [env](#pipeline-env) | `Object` | 环境变量 |
| [imports](#pipeline-imports) | `Array<String>` | 从文件导入环境变量 |
| [label](#pipeline-label) | `Object` | 流水线标签 |
| [stages](#pipeline-stages) | `Array` | 阶段任务列表 |
| [failStages](#pipeline-failstages) | `Array` | 失败时执行的任务 |
| [endStages](#pipeline-endstages) | `Array` | 结束时执行的任务 |
| [ifNewBranch](#pipeline-ifnewbranch) | `Boolean` | 仅新分支时执行 |
| [ifModify](#pipeline-ifmodify) | `Array<String>` | 文件变更时执行 |
| [breakIfModify](#pipeline-breakifmodify) | `Boolean` | 源分支更新时终止 |
| [retry](#pipeline-retry) | `Number` | 失败重试次数 |
| [allowFailure](#pipeline-allowfailure) | `Boolean` | 允许失败 |
| [lock](#pipeline-lock) | `Object` | 流水线锁配置 |
| [sandbox](#pipeline-sandbox) | `Boolean` | 沙箱模式 |

***

### 配置项详细说明

#### name {#pipeline-name}

* **类型**：`String`

指定流水线名，默认为 `pipeline`。当有多条并行流水线时，默认流水线名为 `pipeline`、`pipeline-1`、`pipeline-2` 依此类推，可定义 `name` 指定流水线名来区分不同流水线。

***

#### runner {#pipeline-runner}

* **类型**：`Object`

指定构建节点相关参数。

**子参数**：

| 参数 | 类型 | 说明 |
|------|------|------|
| tags | `String` | `Array<String>` | 指定使用具备哪些标签的构建节点 |
| cpus | `Number` | 指定构建需使用的 cpu 核数 |

##### runner.tags

* **类型**：`String` | `Array<String>`
* **默认值**：`cnb:arch:default`

指定使用具备哪些标签的构建节点。详见[构建节点](/zh/build/build-node.md)。

**示例**：

```yaml title=".cnb.yml"
main:
  push:
    - runner:
        tags: cnb:arch:amd64
      stages:
        - name: uname
          script: uname -a
```

##### runner.cpus {#pipeline-cpus}

* **类型**：`Number`

指定构建需使用的最大 cpu 核数（memory = cpu 核数 × 2 G），其中 cpu 和 memory 不超过 runner 机器实际大小。

未配置，则最大可用 cpu 核数由分配到的 runner 机器配置来指定。

**示例**：

```yaml title=".cnb.yml"
# cpus = 1，memory = 2G
main:
  push:
    - runner:
        cpus: 1
      stages:
        - name: echo
          script: echo "hello world"
```

***

#### docker {#pipeline-docker}

* **类型**：`Object`

指定 `docker` 相关的参数。详情见[构建环境](./build-env.md)

**子参数**：

| 参数 | 说明 |
|------|------|
| image | 当前 Pipeline 的环境镜像，在当前 Pipeline 下的所有任务都将在这个镜像环境中执行 |
| build | 指定一个 Dockerfile，构建一个临时镜像，作为 image 的值使用 |
| devcontainer | 指定 devcontainer.json 文件所在路径，将使用 devcontainer.json 文件内容作为流水线容器镜像 |
| volumes | 声明数据卷，用于缓存场景 |

::: warning 优先级
当同时指定 `image`、`build`、`devcontainer` 时，优先级为 `build` > `devcontainer` > `image`。
:::

##### docker.image {#pipeline-image}

* **类型**：`Object` | `String`

指定当前 `Pipeline` 的环境镜像，在当前 `Pipeline` 下的所有任务都将在这个镜像环境中执行。

该属性及其下的属性支持引用环境变量，参考[变量替换](./env.md#变量替换)。

**属性**：

| 属性 | 类型 | 说明 |
|------|------|------|
| image.name | `String` | 镜像名，如 `node:20` |
| image.dockerUser | `String` | 指定 Docker 用户名，用于拉取指定的镜像 |
| image.dockerPassword | `String` | 指定 Docker 用户密码，用于拉取指定的镜像 |

如果指定 `image` 为字符串，则等同于指定了 `image.name`。

如果使用 `云原生构建` 的 Docker 制品库的镜像且未设置 `image.dockerPassword`，该参数会设为环境变量 [CNB\_TOKEN](./build-in-env.md#cnb_token) 的值。

**示例一：使用 DockerHub 公开镜像**

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        # 取 Docker 官方镜像仓库中的 node:20 镜像作为构建容器
        image: node:20
      stages:
        - name: show version
          script: node -v
```

**示例二：使用 CNB 公开或私有镜像**

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        # 取非公开镜像作为构建容器，需传入 docker 用户名和密码
        image:
          name: docker.cnb.share.ralphlauren.cn/images/pipeline-env:1.0
          # 使用 CNB 镜像，构建时默认注入 CNB_TOKEN 为 dockerPassword
      stages:
        - name: echo
          script: echo "hello world"
```

**示例三：使用其他私有镜像**

在密钥仓库文件中声明拉取其他私有镜像所需的 Docker 用户名、密码。

```yaml title="docker.yml"
DOCKER_USER: <user>
DOCKER_PASSWORD: <password>
```

```yaml title=".cnb.yml"
main:
  push:
    # 导入上文创建的密钥仓库文件为环境变量
    - imports: https://cnb.share.ralphlauren.cn/<your-repo-slug>/-/blob/main/xxx/docker.yml
      docker:
        image:
          name: other-private-images/pipeline-env:1.0
          # 引用密钥仓库文件 docker.yml 声明的环境变量
          dockerUser: $DOCKER_USER
          dockerPassword: $DOCKER_PASSWORD
      stages:
        - name: echo
          script: echo "hello world"
```

##### docker.build {#pipeline-build}

* **类型**：`Object` | `String`

指定一个 `Dockerfile`，构建一个临时镜像，作为 `image` 的值使用。

该属性及其下的属性支持引用环境变量，参考[变量替换](./env.md#变量替换)。

使用 `build` 声明构建环境的完整示例可参考 [docker-build-with-by](https://cnb.cool/examples/ecosystem/docker-build-with-by)。

**参数说明**：

| 参数 | 类型 | 说明 |
|------|------|------|
| build.dockerfile | `String` | Dockerfile 路径，支持引用环境变量 |
| build.target | `String` | 对应 docker build 中的 --target 参数，可以选择性地构建 Dockerfile 中的特定阶段 |
| build.by | `Array<String>` | `String` | 声明缓存构建过程中依赖的文件列表 |
| build.versionBy | `Array<String>` | `String` | 用于版本控制，支持直接传入文件夹路径 |
| build.buildArgs | `Object` | 在 build 时插入额外的构建参数 |
| build.ignoreBuildArgsInVersion | `Boolean` | 版本计算是否忽略 buildArgs |
| build.sync | `String` | 是否等待 `docker push` 成功后才继续，默认为 `false` |

::: warning 注意
未出现在 `by` 列表中的文件，除了 Dockerfile，其他在构建镜像过程中，都当不存在处理。
:::

如果指定 `build` 为字符串，则等同于指定了 `build.dockerfile`。

**Dockerfile 用法**：

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        # `build` 为字符串，则等同于指定了 `build.dockerfile`
        build: ./image/Dockerfile
      stages:
        - stage1
        - stage2
```

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        # 指定了 `build` 的类型为 `Object`，可对构建镜像过程进行更多控制
        build:
          dockerfile: ./image/Dockerfile
          # 只构建 builder，而不是整个 Dockerfile
          target: builder
      stages:
        - stage1
        - stage2
```

**Dockerfile versionBy 用法**：

示例：将 pnpm 缓存到环境镜像中，加速后续 pnpm i 过程

```dockerfile title="Dockerfile"
FROM node:22

RUN npm config set registry https://mirrors.cloud.tencent.com/npm/ \
  && npm i -g pnpm

WORKDIR /data/cache

COPY package.json package-lock.json ./

RUN pnpm i
```

```yaml title=".cnb.yml"
main:
  push:
    # 通过 Dockerfile 指定构建环境
    - docker:
        build:
          dockerfile: ./Dockerfile
          by:
            - package.json
            - package-lock.json
          versionBy:
            - package-lock.json
      stages:
        - name: cp node_modules
          # 从容器中将 node_modules 复制到流水线工作目录
          script: cp -r /data/cache/node_modules ./
        - name: check node_modules
          script: |
            if [ -d "node_modules" ]; then
                cd node_modules
                ls
            else
                echo "node_modules directory does not exist."
            fi
```

##### docker.devcontainer

* **类型**：`String`

指定 `devcontainer.json` 文件所在路径，将使用 `devcontainer.json` 文件内容作为流水线容器镜像。

仅支持相对于当前仓库的路径，如：`.devcontainer/devcontainer.json`

具体 `devcontainer.json` 规范可查看：[devcontainer.json](https://containers.dev/implementors/json_reference/)

::: warning 有限支持
由于 CNB 平台特性，目前仅提供有限的支持。当前支持的属性：

* image
* build.dockerfile
* build.context
* build.args
* build.target
  :::

##### docker.volumes {#pipeline-volumes}

* **类型**：`Array<String>` | `String`

声明数据卷，多个数据卷可用通过数组或者用 `,` 号做分隔符传入，可引用环境变量。

::: warning 注意
此缓存仅在当前构建节点中有效，不支持跨节点缓存。详见[流水线缓存](./pipeline-cache.md)
:::

**支持的格式**：

1. `<group>:<path>:<type>`
2. `<path>:<type>`
3. `<path>`

**参数含义**：

| 参数 | 说明 |
|------|------|
| group | 可选，数据卷分组，不同组间相互隔离 |
| path | 必填，数据卷挂载绝对路径，支持绝对路径（`/` 开头）或相对路径（`./` 开头），相对于工作区 |
| type | 可选，数据卷类型，缺省值为 `copy-on-write` |

**数据卷类型**：

| 类型 | 简写 | 说明 | 适用场景 |
|------|------|------|---------|
| read-write | rw | 读写，并发写冲突需自行处理 | 串行构建场景 |
| read-only | ro | 只读，写操作抛出异常 | 只读访问 |
| copy-on-write | cow | 读写，变更在流水线成功后被合并 | 并发构建场景（默认） |
| copy-on-write-read-only | - | 只读，变更在流水线结束后丢弃 | PR 场景 |
| data | - | 创建临时数据卷，流水线结束时自动清理 | 共享数据 |

###### copy-on-write

用于缓存场景，支持并发。

`copy-on-write` 技术允许系统在需要修改数据之前共享相同的数据副本，从而实现高效的缓存复制。在并发环境中，这种方法避免了缓存的读写冲突，因为只有在实际需要修改数据时，才会创建数据的私有副本。这样，只有写操作会导致数据复制，而读操作可以安全地并行进行，无需担心数据一致性问题。这种机制显著提高了性能，尤其是在读多写少的缓存场景中。

###### data

用于共享数据，将容器中的指定目录，共享给其他容器中使用。

通过创建数据卷，然后 mount 到各容器中。与直接将构建节点上目录 mount 到容器中方式不同的是：当指定的目录在容器中已经存在，会先把容器中内容自动复制到数据卷，而不是将数据卷内容直接覆盖容器中目录。

###### volumes 示例

**示例 1：挂载构建节点上目录到容器中，实现本地缓存效果**

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        image: node:20
        # 声明数据卷
        volumes:
          - /data/config:read-only
          - /data/mydata:read-write
          # 使用缓存，同时更新
          - /root/.npm
          # 使用 main 缓存，同时更新
          - main:/root/.gradle:copy-on-write
      stages:
        - stage1
        - stage2
  pull_request:
    - docker:
        image: node:20
        # 声明数据卷
        volumes:
          - /data/config:read-only
          - /data/mydata:read-write
          # 使用 copy-on-write 缓存
          - /root/.npm
          - node_modules
          # pr 使用 main 缓存，但不更新
          - main:/root/.gradle:copy-on-write-read-only
      stages:
        - stage1
        - stage2
```

**示例 2：将打包在容器中的文件，共享到其他容器中使用**

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        image: go-app-cli # 假设有个 go 应用在镜像的 /go-app/cli 路径下
        # 声明数据卷
        volumes:
          # 此路径在 go-app-cli 镜像存在，所以执行环境镜像时，会将此路径内容复制到临时数据卷中，可共享给其他任务容器里使用
          - /go-app
      stages:
        - name: show /go-app-cli in job container
          image: alpine
          script: ls /go-app
```

***

#### git {#pipeline-git}

* **类型**：`Object`

提供 Git 仓库相关配置。

##### git.enable

* **类型**：`Boolean`
* **默认值**：`true`（`branch.delete` 事件为 `false`）

指定是否拉取代码。

##### git.submodules

* **类型**：`Object` | `Boolean`
* **默认值**：
  * enable: `true`
  * remote: `false`

指定是否要拉取子项目（submodules）。

当值为 `Boolean` 类型时，相当于指定 `git.submodules.enable` 为 `git.submodules` 的值，`git.submodules.remote` 为默认值 `false`。

###### git.submodules.enable

* **类型**：`Boolean`
* **默认值**：`true`

是否指定是否要拉取子项目 `submodules`。

###### git.submodules.remote

* **类型**：`Boolean`
* **默认值**：`false`

执行 `git submodule update` 时是否添加 `--remote` 参数，用于每次拉取 `submodule` 最新的代码。

**基本用法**：

```yaml title=".cnb.yml"
main:
  push:
    - git:
        enable: true
        submodules: true
      stages:
        - name: echo
          script: echo "hello world"
    - git:
        enable: true
        submodules:
          enable: true
          remote: true
      stages:
        - name: echo
          script: echo "hello world"
```

##### git.lfs

* **类型**：`Object` | `Boolean`
* **默认值**：`true`

指定是否要拉取 LFS 文件。

支持 `Object` 形式指定具体参数，字段缺省时，默认值为：

```json
{
  "enable": true
}
```

**基本用法**：

```yaml title=".cnb.yml"
main:
  push:
    - git:
        enable: true
        lfs: true
      stages:
        - name: echo
          script: echo "hello world"
    - git:
        enable: true
        lfs:
          enable: true
      stages:
        - name: echo
          script: echo "hello world"
```

###### git.lfs.enable

是否指定是否要拉取 LFS 文件。

***

#### services {#pipeline-services}

* **类型**：`Array<String>` | `Array<Object>`

用于声明构建时需要的服务，格式：`name:[version]`，`version` 是可选的。

**目前支持的服务**：

* docker
* vscode

##### service:docker {#service-docker}

用于开启 `dind` 服务。

当构建过程中需要使用 `docker build`、`docker login` 等操作时声明，会自动在环境注入 `docker daemon` 和 `docker cli`。

**示例**：

```yaml title=".cnb.yml"
main:
  push:
    - services:
        - docker
      docker:
        image: alpine
      stages:
        - name: docker info
          script:
            - docker info
            - docker ps
```

该服务会自动 `docker login` 到 CNB Docker 制品库的镜像源（\_\_ENV\_\_CNB\_DOCKER\_REGISTRY），在后续任务中可直接 `docker push` 到当前仓库 Docker 制品库。

**示例**：

```yaml title=".cnb.yml"
main:
  push:
    - services:
        - docker
      stages:
        - name: build and push
          script: |
            # 根目录存在 Dockerfile 文件
            docker build -t ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:latest .
            docker push ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:latest
```

若需要使用 `buildx` 构建多种架构/平台的镜像，可以开启 `rootlessBuildkitd` 特性：

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        image: golang:1.24
      services:
        - name: docker
          options:
            rootlessBuildkitd:
              enabled: true
      env:
        IMAGE_TAG: ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:latest
      stages:
        - name: go build
          script: ./build.sh
        - name: docker login
          script: docker login -u ${CNB_TOKEN_USER_NAME} -p "${CNB_TOKEN}" ${CNB_DOCKER_REGISTRY}
        - name: docker build and push
          script: docker buildx build -t ${IMAGE_TAG} --platform linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/loong64,linux/arm/v7,linux/arm/v6 --push .
```

##### service:vscode

需要云原生开发时声明。

**示例**：

```yaml title=".cnb.yml"
$:
  vscode:
    - services:
        - vscode
        - docker
      docker:
        image: alpine
      stages:
        - name: uname
          script: uname -a
```

如需开启仅预览模式，请参考[文档](../workspaces/only-preview.md)。

如需指定开发环境离线保活时间（超过指定时长，则回收开发环境），可使用 `keepAliveTimeout` 参数：

```yaml title=".cnb.yml"
$:
  vscode:
    - docker:
        build: .ide/Dockerfile
      services:
        - docker
        - name: vscode
          options:
            # 保活时间，单位毫秒，不设置默认 10 分钟没有心跳（检测不到开发环境内的 http/ssh 连接）就关闭开发环境
            keepAliveTimeout: 10m
      # 开发环境启动后会执行的任务
      stages:
        - name: ls
          script: ls -al
```

**keepAliveTimeout 参数说明**：

| 属性 | 值 |
|------|-----|
| 类型 | `Number` | `String`（单位默认为毫秒） |
| 默认值 | `600000`（ms, 10分钟） |
| 说明 | 开发环境的离线保活时间 |

支持的时间单位：

| 单位 | 说明 |
|------|------|
| ms | 毫秒（默认） |
| s | 秒 |
| m | 分钟 |
| h | 小时 |

***

#### env {#pipeline-env}

* **类型**：`Object`

声明一个对象作为环境变量：属性名为环境变量名，属性值为环境变量值。

在任务执行中使用，对当前 `Pipeline` 内的非插件任务均有效。

**示例**：

```yaml title=".cnb.yml"
main:
  push:
    - services:
        - docker
      env:
        some_key: some_value
      stages:
        - name: some job
          script: echo "$some_value"
```

***

#### imports {#pipeline-imports}

* **类型**：`Array<String>` | `String`

指定 CNB 仓库文件路径（文件相对路径或页面地址），作为环境变量来源，作用同 `env`。

文件内容会被解析为对象，属性名为环境变量名，属性值为环境变量值。

在任务执行中使用，对当前 `Pipeline` 内的非插件任务均有效。

本地相对路径如 `./env.yml` 会被拼接成文件页面地址进行加载。即：**本地存在但远端不存在的文件不会被引用**。

::: tip 安全性
`云原生构建` 现支持[密钥仓库](../repo/secret.md)，安全性更高，支持文件引用审计。一般使用一个密钥仓库来存放诸如 `npm`、`docker` 等账号密码。
:::

##### 同名 key 优先级

* 当配置 imports 为数组时，如遇到参数重复的情况，后面的配置会覆盖前面的
* 如果和 `env` 参数中重复，那么 `env` 中的参数会覆盖掉 `imports` 文件中的

##### 变量替换

`imports` 文件路径可读取环境变量。若是数组，下面的文件路径可读取上面文件中的变量。

```yaml title="env1.yml"
FILE: "https://cnb.share.ralphlauren.cn/<your-repo-slug>/-/blob/main/xxx/env2.yml"
```

```yaml title="env2.yml"
TEST_TOKEN: some token
```

```yaml title=".cnb.yml"
main:
  push:
    - imports:
        - ./env1.yml
        # FILE 为 env1.yml 中声明的环境变量
        - $FILE
      stages:
        - name: echo
          # TEST_TOKEN 为 env2.yml 中声明的环境变量
          script: echo $TEST_TOKEN
```

##### 引用鉴权

被引用文件可声明可被访问范围，参考 [配置文件引用鉴权](./file-reference.md#权限检查)。

**示例**：

匹配一个项目下面的所有仓库：

```yaml
key: value

allow_slugs:
  - team_name/project_name/**
```

允许被所有仓库引用：

```yaml
key: value

allow_slugs:
  - "**"
```

##### 文件解析规则

支持解析多种文件格式并将其转换为环境变量。

**YAML 文件**

按 YAML 格式解析为对象，支持的文件格式为：`.yaml`、`.yml`。

```yaml title="env.yml"
DOCKER_USER: "username"
DOCKER_TOKEN: "token"
DOCKER_REGISTRY: "https://xxx/xxx"
```

**JSON 文件**

按 JSON 格式解析为对象，支持的文件格式为：`.json`。

```json title="env.json"
{
  "DOCKER_USER": "username",
  "DOCKER_TOKEN": "token",
  "DOCKER_REGISTRY": "https://xxx/xxx"
}
```

**证书文件**

证书文件以文件名（`.` 被替换为 `_`）作为属性名，文件内容作为属性值解析为对象。

支持的文件格式包括：`.crt`、`.cer`、`.key`、`.pem`、`.pub`、`.pk`、`.ppk`。

```bash title="server.crt"
-----BEGIN CERTIFICATE-----
MIIE...
-----END CERTIFICATE-----
```

```yaml title=".cnb.yml"
main:
  push:
    - imports: https://cnb.share.ralphlauren.cn/<your-repo-slug>/-/blob/main/server.crt
      stages:
        - echo "$server_crt" > server.crt
        - cat server.crt
```

**其他文本文件**

除以上文件格式外，其他文本文件按 `key=value` 的格式解析为对象。

```bash title="dev.env"
DB_HOST=localhost
DB_PORT=5432
```

###### 深层属性

大部分场景配置文件是简单的单层属性，比如：

```javascript
// env.json
{
  "token": "private token",
  "password": "private password"
}
```

为了应对复杂的配置文件和场景，深层属性（第一层不能为数组），会平铺成单层对象，规则是：

1. 属性名保留，属性值会转成字符串
2. 属性值若为对象（包括数组），则会递归平铺，属性路径以 `_` 连接

```javascript title="env.json"
{
  "key1": [
    "value1",
    "value2"
  ],
  "key2": {
    "subkey1": [
      "value3",
      "value4"
    ],
    "subkey2": "value5"
  },
  "key3": [
    "value6",
    {
      "subsubkey1": "value7"
    }
  ],
  "key4": "value8"
}
```

会平铺成：

```javascript
{
  // 原属性值转成字符串
  "key1": "value1,value2",
  // 属性值若为对象，则额外进行递归平铺操作增加属性
  "key1_0": "value1",
  "key1_1": "value2",
  "key2": "[object Object]",
  "key2_subkey1": "value3,value4",
  "key2_subkey1_0": "value3",
  "key2_subkey1_1": "value4",
  "key2_subkey2": "value5",
  "key3": "value6,[object Object]",
  "key3_0": "value6",
  "key3_1": "[object Object]",
  "key3_1_subsubkey1": "value7",
  "key4": "value8"
}
```

```yaml title=".cnb.yml"
main:
  push:
    - imports:
        - ./env.json
      stages:
        - name: echo
          script: echo $key3_1_subsubkey1
```

***

#### label {#pipeline-label}

* **类型**：`Object`

为流水线指定标签。每个标签的值可以是一个字符串，也可以是一个字符串数组。该标签可用于后续流水线记录筛选等功能。

这里举一种工作流的例子：**main 分支合并即发布预发布环境，打 tag 后发布正式环境**。

```yaml title=".cnb.yml"
main:
  push:
    - label:
        # Master 分支的常规流水线
        type:
          - MASTER
          - PREVIEW
      stages:
        - name: install
          script: npm install
        - name: CCK-lint
          script: npm run lint
        - name: BVT-build
          script: npm run build
        - name: UT-test
          script: npm run test
        - name: pre release
          script: ./pre-release.sh

$:
  tag_push:
    - label:
        # 产品发布分支的常规流水线
        type: RELEASE
      stages:
        - name: install
          script: npm install
        - name: build
          script: npm run build
        - name: DELIVERY-release
          script: ./release.sh
```

***

#### stages {#pipeline-stages}

* **类型**：`Array<Stage|Job>`

定义一组阶段任务，每个阶段串行运行。

***

#### failStages {#pipeline-failstages}

* **类型**：`Array<Stage|Job>`

定义一组失败阶段任务。当正常流程失败，会依次执行此阶段任务。

***

#### endStages {#pipeline-endstages}

* **类型**：`Array<Stage|Job>`

定义流水线结束阶段执行的一组任务。当流水线 stages/failStages 执行完，流水线结束前，会依次执行此阶段任务。

当流水线 prepare 阶段成功，无论 stages 是否成功，endStages 都将执行。且 endStages 是否成功不影响流水线状态（即 endStages 失败，流水线状态也可能是成功）。

***

#### ifNewBranch {#pipeline-ifnewbranch}

* **类型**：`Boolean`
* **默认值**：`false`

为 `true` 表示当前分支属于新分支（即 `CNB_IS_NEW_BRANCH` 为 `true`）时，才执行此 `Pipeline`。

::: tip 条件组合
当同时存在 `ifNewBranch` / `ifModify` 时，其中有一个条件满足，此 `Pipeline` 就会执行。
:::

***

#### ifModify {#pipeline-ifmodify}

* **类型**：`Array<String>` | `String`

指定只有相应文件变动时，才执行此 `Pipeline`。是一个 `glob` 表达式字符串或字符串数组。

##### 支持事件

* 非新建分支的 `push` 事件，会对比 `before` 和 `after`，统计变更文件
* `commit.add` 事件，会统计新增 commit 中变更的文件
* 非新建分支的 `push`、`commit.add` 事件流水线中通过 `cnb:apply` 触发的事件，变更文件统计规则同上
* `PR` 触发的事件，统计 `PR` 中的变更文件
* `PR` 触发的事件通过 `cnb:apply` 触发的事件，统计 `PR` 中的变更文件

::: warning 限制
因为文件变更可能非常多，变更文件的统计限制为最多 300 个。
上述情况外，不适合统计文件变更，会忽略 `ifModify` 检查。
:::

##### 示例 {#pipeline-ifmodify-examples}

**示例 1**：当修改文件列表中包含 `a.js` 或者 `b.js`，会执行此 `Pipeline`。

```yaml
ifModify:
  - a.js
  - b.js
```

**示例 2**：当修改文件列表中包含有 `js` 后缀的文件时，会执行此 `Pipeline`。

```yaml
ifModify:
  - "**/*.js"
  - "*.js"
```

**示例 3**：反向匹配，排除目录 legacy 和排除所有 Markdown 文件，有其他文件变更时触发。

```yaml
ifModify:
  - "**"
  - "!(legacy/**)"
  - "!(**/*.md)"
  - "!*.md"
```

**示例 4**：反向匹配，src 目录并且除目录 src/legacy 以外有变更时触发。

```yaml
ifModify:
  - "src/**"
  - "!(src/legacy/**)"
```

***

#### breakIfModify {#pipeline-breakifmodify}

* **类型**：`Boolean`
* **默认值**：`false`

`Job` 执行前，如果源分支已更新，则终止构建。

***

#### retry {#pipeline-retry}

* **类型**：`Number`
* **默认值**：`0`

失败重试次数，`0` 表示不重试。

重试间隔为 1s, 2s, 4s, 8s...

***

#### allowFailure {#pipeline-allowfailure}

* **类型**：`Boolean`
* **默认值**：`false`

是否允许当前流水线失败。

当此参数设置为 `true` 时，流水线的失败的状态不会上报到 CNB 上。

***

#### lock {#pipeline-lock}

* **类型**：`Object` | `Boolean`

给 `pipeline` 设置锁，`pipeline` 执行完后自动释放锁，锁不能跨仓库使用。

**表现**：流水线 A 获取到锁后，流水线 B 再申请锁，可以终止 A 或等待 A 执行完释放锁后，获取到锁再继续执行任务。

**参数说明**：

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| key | `String` | 分支名-流水线名 | 自定义锁名 |
| expires | `Number` | 3600（1小时） | 锁过期时间（秒） |
| timeout | `Number` | 3600（1小时） | 超时时间（秒） |
| cancel-in-progress | `Boolean` | false | 是否终止占用锁或等待锁的流水线 |
| wait | `Boolean` | false | 锁被占用是否等待 |
| cancel-in-wait | `Boolean` | false | 是否终止正在等待锁的流水线 |

若 `lock` 为 true，则所有参数为默认值。

**示例 1**：lock 是 Boolean 格式

```yaml title=".cnb.yml"
main:
  push:
    - lock: true
      stages:
        - name: stage1
          script: echo "stage1"
```

**示例 2**：lock 是 Object 格式

```yaml title=".cnb.yml"
main:
  push:
    - lock:
        key: key
        expires: 600 # 10分钟
        wait: true
        timeout: 60 # 最多等待 1分钟
      stages:
        - name: stage1
          script: echo "stage1"
```

**示例 3**：停止 pull\_request 下上一条正在进行的流水线

```yaml title=".cnb.yml"
main:
  pull_request:
    - lock:
        key: pr
        cancel-in-progress: true
      stages:
        - name: echo hello
          script: echo "stage1"
```

***

#### sandbox {#pipeline-sandbox}

* **类型**：`Boolean`

是否开启沙箱模式。

设为 `true` 时，流水线以下环境变量无效：

* [CNB\_TOKEN](./build-in-env.md#cnb_token)
* [CNB\_TOKEN\_FOR\_AI](./build-in-env.md#cnb_token_for_ai)

## Stage

* **类型**：`Job` | `Object<name: Job>`

`Stage` 表示流水线中的一个阶段，可包含一个或多个 `Job`。详见 [Job 介绍](#job)。

### 配置项概览

Stage 支持以下配置项：

| 配置项 | 类型 | 说明 |
|--------|------|------|
| [name](#Stage-name) | `String` | 阶段名称 |
| [ifNewBranch](#stage-ifnewbranch) | `Boolean` | 仅新分支时执行 |
| [ifModify](#stage-ifmodify) | `Array<String>` | 文件变更时执行 |
| [if](#stage-if) | `String` | 条件执行脚本 |
| [env](#stage-env) | `Object` | 环境变量 |
| [imports](#stage-imports) | `Array<String>` | 从文件导入环境变量 |
| [retry](#stage-retry) | `Number` | 失败重试次数 |
| [lock](#stage-lock) | `Object` | 阶段锁配置 |
| [image](#Stage-image) | `Object` | 环境镜像 |
| [jobs](#stage-jobs) | `Array` | 任务列表 |

### 结构说明

#### 单个 Job

当 `Stage` 只有一个 `Job` 时，可省略 `Stage` 层级，直接声明 `Job`。

```yaml
stages:
  - name: stage1
    jobs:
      - name: job A
        script: echo hello
```

可以简化为：

```yaml
- stages:
    - name: job A
      script: echo hello
```

**当 `Job` 为字符串时，可视作脚本任务，name 和 script 都取该字符串，可继续简化为**：

```yaml
- stages:
    - echo hello
```

#### 串行 Job

当 `jobs` 为数组时，这组 `Job` 会按顺序串行执行。

```yaml
# 串行执行
stages:
  - name: install
    jobs:
      - name: job1
        script: echo "job1"
      - name: job2
        script: echo "job2"
```

#### 并行 Job

当 `jobs` 为对象时，这组 `Job` 会并行执行。

```yaml
# 并行执行
stages:
  - name: install
    jobs:
      job1:
        script: echo "job1"
      job2:
        script: echo "job2"
```

多个 `Job` 可灵活组织串行和并行。先串行后并行的示例：

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - name: serial first
          script: echo "serial"
        - name: parallel
          jobs:
            parallel job 1:
              script: echo "1"
            parallel job 2:
              script: echo "2"
        - name: serial next
          script: echo "serial next"
```

***

### 配置项说明

#### name {#Stage-name}

* **类型**：`String`

阶段名称。

***

#### ifNewBranch {#stage-ifnewbranch}

* **类型**：`Boolean`
* **默认值**：`false`

为 `true` 表示当前分支属于新分支（即 `CNB_IS_NEW_BRANCH` 为 `true`）时，才执行此 `Stage`。

::: tip 条件组合
当同时存在 `ifNewBranch` / `ifModify` / `if` 时，其中有一个条件满足，此 `Stage` 就会执行。
:::

***

#### ifModify {#stage-ifmodify}

* **类型**：`Array<String>` | `String`

指定只有相应文件变动时，才执行此 `Stage`。使用 `glob` 匹配表达式。

***

#### if {#stage-if}

* **类型**：`Array<String>` | `String`

指定一个 shell 脚本，当脚本退出码为 `0` 时，执行此 `Stage`。

**示例 1：判断变量值**

[shell 表达式语法](https://linux.die.net/man/1/test)

```yaml title=".cnb.yml"
main:
  push:
    - env:
        IS_NEW: true
      stages:
        - name: is new
          if: |
            [ "$IS_NEW" = "true" ]
          script: echo is new
        - name: is not new
          if: |
            [ "$IS_NEW" != "true" ]
          script: echo not new
```

**示例 2：判断任务输出**

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - name: make info
          script: echo 'haha'
          exports:
            info: RESULT
        - name: run if RESULT is haha
          if: |
            [ "$RESULT" = "haha" ]
          script: echo $RESULT
```

***

#### env {#stage-env}

* **类型**：`Object`

声明环境变量，仅对当前 `Stage` 生效。

`Stage env` 优先级比 `Pipeline env` 高。

***

#### imports {#stage-imports}

* **类型**：`Array<String>` | `String`

从文件导入环境变量，仅对当前 `Stage` 生效。用法同 [Pipeline imports](#pipeline-imports)。

***

#### retry {#stage-retry}

* **类型**：`Number`
* **默认值**：`0`

失败重试次数，`0` 表示不重试。

重试间隔为 1s, 2s, 4s, 8s...

***

#### lock {#stage-lock}

* **类型**：`Boolean` | `Object`

给 `Stage` 设置锁，`Stage` 执行完后自动释放锁。

**表现**：任务 A 获取到锁后，任务 B 需等待锁释放后才能继续执行。

**参数说明**：

| 参数 | 类型 | 说明 |
|------|------|------|
| lock.key | `String` | 自定义锁名，默认为 `分支名-流水线名-stage下标` |
| lock.expires | `Number` | 锁过期时间（秒），默认 3600 |
| lock.wait | `Boolean` | 锁被占用时是否等待，默认 `false` |
| lock.timeout | `Number` | 等待锁的超时时间（秒），默认 3600 |

若 `lock` 为 `true`，则所有参数为默认值。

**示例 1**：`lock` 是 `Boolean` 格式

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - name: stage1
          lock: true
          jobs:
            - name: job1
              script: echo "job1"
```

**示例 2**：`lock` 是 `Object` 格式

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - name: stage1
          lock:
            key: key
            expires: 600 # 10分钟
            wait: true
            timeout: 60 # 最多等待 1分钟
          jobs:
            - name: job1
              script: echo "job1"
```

***

#### Stage.image {#Stage-image}

* **类型**：`Object` | `String`

指定当前 `Stage` 的环境镜像，在当前 `Stage` 下的所有任务默认都在这个镜像环境中执行。

该属性支持引用环境变量，参考[变量替换](./env.md#变量替换)。

**属性**：

| 属性 | 类型 | 说明 |
|------|------|------|
| image.name | `String` | 镜像名，如 `node:20` |
| image.dockerUser | `String` | 指定 Docker 用户名，用于拉取指定的镜像 |
| image.dockerPassword | `String` | 指定 Docker 用户密码，用于拉取指定的镜像 |

如果指定 `image` 为字符串，则等同于指定了 `image.name`。

如果使用 `云原生构建` 的 Docker 制品库的镜像且未设置 `image.dockerPassword`，该参数会设为环境变量 [CNB\_TOKEN](./build-in-env.md#cnb_token) 的值。

***

#### jobs {#stage-jobs}

* **类型**：`Array<Job>` | `Object<name,Job>`

定义一组任务。数组形式为串行执行，对象形式为并行执行。

## Job

`Job`（任务）是最小的执行单元，主要分为**脚本任务**、**插件任务**和**内置任务**三种类型。前两者都支持指定 `image` 参数定义运行环境，这容易让新人混淆。参考[脚本任务 vs 插件任务](./script-vs-plugin.md)了解两者的区别。

### 配置项概览

Job 支持以下配置项：

| 配置项 | 类型 | 说明 |
|--------|------|------|
| [name](#job-name) | `String` | 任务名称 |
| [ifModify](#job-ifmodify) | `Array<String>` | 文件变更时执行 |
| [ifNewBranch](#job-ifnewbranch) | `Boolean` | 仅新分支时执行 |
| [if](#job-if) | `String` | 条件执行脚本 |
| [breakIfModify](#job-breakifmodify) | `Boolean` | 源分支更新时终止 |
| [skipIfModify](#job-skipifmodify) | `Boolean` | 源分支更新时跳过 |
| [env](#job-env) | `Object` | 环境变量 |
| [imports](#job-imports) | `Array<String>` | 从文件导入环境变量 |
| [exports](#job-exports) | `Object` | 导出环境变量 |
| [timeout](#job-timeout) | `Number` | 超时时间 |
| [allowFailure](#job-allowfailure) | `Boolean` | 允许失败 |
| [lock](#job-lock) | `Object` | 任务锁配置 |
| [retry](#job-retry) | `Number` | 失败重试次数 |
| [type](#job-type) | `String` | 内置任务类型 |
| [options](#job-options) | `Object` | 内置任务参数 |
| [optionsFrom](#job-optionsfrom) | `Array<String>` | 从文件加载参数 |
| [script](#job-script) | `String` | Shell 脚本 |
| [commands](#job-commands) | `String` | Shell 脚本（兼容 Drone CI） |
| [image](#job-image) | `Object` | 运行环境镜像 |
| [settings](#job-settings) | `Object` | 插件参数 |
| [settingsFrom](#job-settingsfrom) | `Array<String>` | 从文件加载插件参数 |
| [args](#job-args) | `Array<String>` | 插件参数 |

### 任务类型

#### 脚本任务 {#job-script-task}

执行 Shell 脚本命令。

**参数说明**：

| 参数 | 类型 | 说明 |
|------|------|------|
| script | `Array<String>` | `String` | 要执行的 Shell 脚本，数组会用 `&&` 连接 |
| image | `String` | 可选，指定脚本运行环境 |

**示例**：

```yaml
- name: install
  image: node:20
  script: npm install
```

**简化写法**：脚本任务可简化为字符串，此时 `script` 和 `name` 都取该字符串：

```yaml
- echo hello
```

等同于：

```yaml
- name: echo hello
  script: echo hello
```

***

#### 插件任务

插件即 Docker 镜像，也可称为镜像任务。

**特点**：

* 执行环境灵活
* 易于分享和复用
* 可跨 CI 平台使用

**工作原理**：通过向 `ENTRYPOINT` 传递环境变量来配置插件行为。

::: warning 注意
通过 `imports`、`env` 设置的自定义环境变量不会传递给插件，但可在 `settings`、`args` 中使用变量替换。CNB 系统环境变量会传递给插件。
:::

**参数说明**：

| 参数 | 类型 | 说明 |
|------|------|------|
| name | `String` | 任务名称 |
| image | `Object` | `String` | 插件镜像，详见 [job.image](#job-image) |
| settings | `Object` | 插件参数，按插件文档填写，支持 `$VAR` 或 `${VAR}` 引用环境变量 |
| settingsFrom | `Array<String>` | `String` | 指定本地或 Git 仓库文件路径，加载为插件任务参数 |

##### 示例 {#plugin-example}

**with imports**：

```yaml
- name: npm publish
  image: plugins/npm
  imports: https://cnb.share.ralphlauren.cn/<your-repo-slug>/-/blob/main/xxx/npm.json
  settings:
    username: $NPM_USER
    password: $NPM_PASS
    email: $NPM_EMAIL
    registry: https://mirrors.xxx.com/npm/
    folder: ./
```

```json
{
  "username": "xxx",
  "password": "xxx",
  "email": "xxx@emai.com",
  "allow_slugs": ["cnb/**/**"],
  "allow_images": ["plugins/npm"]
}
```

**with settingsFrom**：

```yaml
- name: npm publish
  image: plugins/npm
  settingsFrom: https://cnb.share.ralphlauren.cn/<your-repo-slug>/-/blob/main/xxx/npm-settings.json
  settings:
    registry: https://mirrors.xxx.com/npm/
    folder: ./
```

```json
{
  "username": "xxx",
  "password": "xxx",
  "email": "xxx@emai.com",
  "allow_slugs": ["cnb/cnb"],
  "allow_images": ["plugins/npm"]
}
```

***

#### 内置任务

使用 CNB 提供的内置功能。

**参数说明**：

| 参数 | 类型 | 说明 |
|------|------|------|
| type | `String` | 指定要执行的 [内置任务](./internal-steps/) 类型 |
| options | `Object` | 内置任务的参数 |
| optionsFrom | `Array<String>` | `String` | 从文件加载参数 |

`options` 中的字段优先级高于 `optionsFrom`。

引用配置文件权限控制参考 [配置文件引用鉴权](./file-reference.md#权限检查)。

**示例**：

```yaml
name: install
type: INTERNAL_JOB_NAME
optionsFrom: ./options.json
options:
  key1: value1
  key2: value2
```

```json
// ./options.json
{
  "key1": "value1",
  "key2": "value2"
}
```

***

### 配置项说明

#### name {#job-name}

* **类型**：`String`

任务名称。

***

#### ifModify {#job-ifmodify}

* **类型**：`Array<String>` | `String`

指定只有相应文件变动时，才执行此任务。用法同 [Stage ifModify](#stage-ifmodify)。

***

#### ifNewBranch {#job-ifnewbranch}

* **类型**：`Boolean`
* **默认值**：`false`

为 `true` 表示当前分支属于新分支（即 `CNB_IS_NEW_BRANCH` 为 `true`）时，才执行此任务。用法同 [Stage ifNewBranch](#stage-ifnewbranch)。

***

#### if {#job-if}

* **类型**：`Array<String>` | `String`

指定一个 shell 脚本，当脚本退出码为 `0` 时，执行此任务。用法同 [Stage if](#stage-if)。

***

#### breakIfModify {#job-breakifmodify}

* **类型**：`Boolean`
* **默认值**：`false`

`Job` 执行前，如果源分支已更新，则终止当前任务。

***

#### skipIfModify {#job-skipifmodify}

* **类型**：`Boolean`
* **默认值**：`false`

`Job` 执行前，如果源分支已更新，则跳过当前任务。

***

#### env {#job-env}

* **类型**：`Object`

声明环境变量，仅对当前 `Job` 生效。

`Job env` 优先级最高。

***

#### imports {#job-imports}

* **类型**：`Array<String>` | `String`

从文件导入环境变量，仅对当前 `Job` 生效。用法同 [Stage imports](#stage-imports)。

***

#### exports {#job-exports}

* **类型**：`Object`

将任务执行结果导出为环境变量，供后续任务使用。生命周期为当前 `Pipeline`。

详情请见 [环境变量](./env.md#导出环境变量)

***

#### timeout

* **类型**：`Number` | `String`

设置单个任务的超时时间和无输出超时时间，默认单个任务的超时时间为 1 小时，最大不能超过 12 小时，无输出超时时间为 10 分钟。

对脚本任务和插件任务有效。

**支持的时间单位**：

| 单位 | 说明 |
|------|------|
| ms | 毫秒（默认） |
| s | 秒 |
| m | 分钟 |
| h | 小时 |

**示例**：

```yaml
name: timeout job
script: sleep 1d
timeout: 100s # 任务将在100秒后超时退出
```

详见 [超时策略](./timeout.md)

***

#### allowFailure {#job-allowfailure}

* **类型**：`Boolean` | `String`
* **默认值**：`false`

是否允许当前任务失败。

当此参数设置为 `true` 时，任务失败不会影响后续流程的执行和最终结果。

值为 `String` 类型时可读取环境变量。

***

#### lock {#job-lock}

* **类型**：`Object` | `Boolean`

给 `Job` 设置锁，`Job` 执行完后自动释放锁。

**表现**：任务 A 获取到锁后，任务 B 需等待锁释放后才能继续执行。

**参数说明**：

| 参数 | 类型 | 说明 |
|------|------|------|
| lock.key | `String` | 自定义锁名，默认为 `分支名-流水线名-stage下标-job名` |
| lock.expires | `Number` | 锁过期时间（秒），默认 3600 |
| lock.wait | `Boolean` | 锁被占用时是否等待，默认 `false` |
| lock.timeout | `Number` | 等待锁的超时时间（秒），默认 3600 |

若 `lock` 为 `true`，则所有参数为默认值。

**示例 1：简化写法**

```yaml
name: 锁
lock: true
script: echo 'job 锁'
```

**示例 2：详细配置**

```yaml
name: 锁
lock:
  key: key
  expires: 10
  wait: true
script: echo 'job 锁'
```

***

#### retry {#job-retry}

* **类型**：`Number`
* **默认值**：`0`

失败重试次数，`0` 表示不重试。

重试间隔为 1s, 2s, 4s, 8s...

***

#### type {#job-type}

* **类型**：`String`

指定要执行的 [内置任务](./internal-steps/) 类型。

***

#### options {#job-options}

* **类型**：`Object`

内置任务的参数配置。

***

#### optionsFrom {#job-optionsfrom}

* **类型**：`Array<String>` | `String`

从文件加载内置任务参数。与 `imports` 参数类似，配置为数组时，后面的配置会覆盖前面的。

`options` 中的字段优先级高于 `optionsFrom`。

***

#### script {#job-script}

* **类型**：`Array<String>` | `String`

要执行的 Shell 脚本。为数组时会自动使用 `&&` 拼接。脚本的退出码作为任务的退出码。

::: warning 注意
流水线默认使用 `sh` 作为命令行解释器，不同的 `image` 可能使用不同的解释器。
:::

***

#### commands {#job-commands}

* **类型**：`Array<String>` | `String`

作用同 `script` 参数，优先级比 `script` 高。主要为了兼容 `Drone CI` 语法。

***

#### Job.image {#job-image}

* **类型**：`Object` | `String`

指定任务的运行环境镜像。可用于脚本任务或插件任务。

该属性支持引用环境变量，参考[变量替换](./env.md#变量替换)。

**属性**：

| 属性 | 类型 | 说明 |
|------|------|------|
| image.name | `String` | 镜像名，如 `node:20` |
| image.dockerUser | `String` | 指定 Docker 用户名，用于拉取指定的镜像 |
| image.dockerPassword | `String` | 指定 Docker 用户密码，用于拉取指定的镜像 |

如果指定 `image` 为字符串，则等同于指定了 `image.name`。

如果使用 `云原生构建` 的 Docker 制品库的镜像且未设置 `image.dockerPassword`，该参数会设为环境变量 [CNB\_TOKEN](./build-in-env.md#cnb_token) 的值。

***

#### settings {#job-settings}

* **类型**：`Object`

插件任务的参数配置。按照插件文档填写，支持通过 `$VAR` 或 `${VAR}` 引用环境变量。

***

#### settingsFrom {#job-settingsfrom}

* **类型**：`Array<String>` | `String`

从文件加载插件任务参数。与 `imports` 参数类似。

**优先级**：

* 配置为数组时，后面的配置会覆盖前面的
* `settings` 中的字段优先级高于 `settingsFrom`

引用配置文件权限控制参考 [配置文件引用鉴权](./file-reference.md#权限检查)。

***

#### args {#job-args}

* **类型**：`Array<String>`

执行插件镜像时传递的参数，内容会追加到 `ENTRYPOINT` 中，仅支持数组。

**示例**：

```yaml
- name: npm publish
  image: plugins/npm
  args:
    - ls
```

等同于执行：

```bash
docker run plugins/npm ls
```

***

### 任务退出码说明

任务执行完成后会返回一个退出码，不同的退出码有不同的含义：

| 退出码 | 说明 |
|--------|------|
| 0 | 任务成功，继续执行后续任务 |
| 78 | 任务成功，但中断当前流水线的执行（可在脚本中主动执行 `exit 78` 来中断流水线） |
| 其他数字 | 任务失败，同时中断当前流水线的执行 |

## 配置复用

### include {#include}

使用 `include` 关键字，可以在当前配置文件中导入当前仓库或其他仓库中的 YAML 文件。这有助于对大型配置进行拆分，提高可维护性和复用性。

#### 使用示例

**template.yml**（被引用的文件）：

```yaml title="template.yml"
main:
  push:
    pipeline_2:
      env:
        ENV_KEY1: xxx
        ENV_KEY3: inner
      services:
        - docker
      stages:
        - name: echo
          script: echo 222
```

**.cnb.yml**（主配置文件）：

```yaml title=".cnb.yml"
include:
  - https://cnb.share.ralphlauren.cn/<your-repo-slug>/-/blob/main/xxx/template.yml

main:
  push:
    pipeline_1:
      stages:
        - name: echo
          script: echo 111
    pipeline_2:
      env:
        ENV_KEY2: xxx # 新增环境变量
        ENV_KEY3: outer # 覆盖 template.yml 中的 ENV_KEY3
      stages:
        - name: echo
          script: echo 333 # 新增步骤
```

**合并后的等效配置**：

```yaml title=".cnb.yml"
main:
  push:
    pipeline_1: # key不存在，合并时新增
      stages:
        - name: echo
          script: echo 111
    pipeline_2: # key已存在，合并内容
      env: # 对象合并：同名键覆盖，新键添加
        ENV_KEY1: xxx # 来自 template.yml
        ENV_KEY2: xxx # 来自 .cnb.yml (新增)
        ENV_KEY3: outer # 来自 .cnb.yml (覆盖)
      services: # 数组合并：追加元素
        - docker # 来自 template.yml
      stages: # 数组合并：追加元素
        - name: echo # 来自 template.yml
          script: echo 222
        - name: echo # 来自 .cnb.yml
          script: echo 333
```

***

#### 语法说明

`include` 支持三种引入方式：

```yaml title=".cnb.yml"
include:
  # 1. 直接传入配置文件路径 (字符串)
  - "https://cnb.share.ralphlauren.cn/<your-repo-slug>/-/blob/main/xxx/template.yml"
  - "template.yml" # 相对路径（相对于仓库根目录）

  # 2. 传入对象，提供更多控制
  # path: 配置文件路径
  # ignoreError: 未找到文件时是否报错。true-忽略错误；false-报错（默认）
  - path: "template1.yml"
    ignoreError: true

  # 3. 直接传入内联的 YAML 配置对象
  - config:
      main:
        push:
          - stages:
              - name: echo
                script: echo "hello world"
```

***

#### 合并规则

不同文件间的配置按以下规则合并：

| 场景 | 合并结果 |
|------|---------|
| 数组 + 数组 | 合并所有元素（追加） |
| 对象 + 对象 | 合并键，同名键的值会被覆盖 |
| 数组 + 对象 或 对象 + 数组 | 最终结果仅为数组（对象被忽略） |

**合并顺序**：

* 本地 `.cnb.yml` 配置**覆盖** `include` 引入的配置
* `include` 数组中**后面**的配置覆盖**前面**的配置

::: warning 权限说明
出于安全考虑，与敏感信息保护原则一致，`include` **无法**引用存储在**密钥仓库**中的文件，因为合并后的完整配置会展示在构建详情页中。
:::

::: tip 注意事项

1. 支持嵌套 include，但最多不能超过 50 个配置文件
2. include 的本地文件路径相对于项目根目录
3. 不支持引用 git submodule 中的文件
4. **不支持**跨文件使用 YAML 锚点 (`&`, `*`)
   :::
