---
url: /zh/build/grammar.md
description: CNB 流水线 .cnb.yml 配置文件的完整语法参考，详细说明 Pipeline、Stage、Job 三层结构的所有配置项。
---
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/*` |
| 兜底匹配  | 匹配所有未明确指定的分支 | `$`                    |

#### 分支匹配示例

**精确匹配**——只在 `main` 分支触发：

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - echo "main branch"
```

**通配符匹配**——匹配所有 `feature` 开头的分支：

```yaml title=".cnb.yml"
feature/*:
  push:
    - stages:
        - echo "feature branch"
```

**兜底匹配**——匹配其他所有分支：

```yaml title=".cnb.yml"
$:
  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 核数                |
| namespace | `String`                    | 指定构建节点命名空间                   |

##### 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 核数（内存 = CPU 核数 × 2 GiB），不超过构建节点的实际规格。

未配置时，可用的最大 CPU 核数由分配到的构建节点决定。

**示例**：

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

##### runner.namespace

* **类型**：`String`
* **可选值**：`group`

指定构建节点的命名空间。
如需使用当前仓库所属根组织下配置的自托管构建节点，填写 `group`。
不设置时使用平台默认分配。

使用 `group` 时必须同时配置 `runner.tags`，用于匹配组织内的自托管构建节点。

**示例**：

```yaml title=".cnb.yml"
main:
  push:
    - runner:
        namespace: group
        tags: my-runner
      stages:
        - name: build
          script: echo "running on self-hosted runner"
```

***

#### docker {#pipeline-docker}

* **类型**：`Object`

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

**子参数**：

| 参数           | 说明                               |
| ------------ | -------------------------------- |
| image        | 当前 Pipeline 的环境镜像                |
| build        | 指定 Dockerfile 构建临时镜像作为 image 的值  |
| devcontainer | 使用 devcontainer.json 内容作为流水线容器镜像 |
| volumes      | 声明数据卷，用于缓存场景                     |

* **image**：在当前 Pipeline 下的所有任务都将在这个镜像环境中执行
* **devcontainer**：指定 devcontainer.json 文件所在路径

::: 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`，系统会自动注入一个**具备制品读取权限**的临时令牌用于拉取镜像。
该令牌以触发者身份创建、具备制品读取权限、流水线结束时自动销毁，并遵循「公开仓库不能访问私有仓库资源」原则：

* **公开仓库**：仅能拉取公开的制品；
* **私有仓库**：不限制仓库范围，可跨仓库拉取触发者有权访问的私有制品。
  :::

**示例一：使用 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.cnbprod.alpha.qq.com/images/pipeline-env:1.0
          # 使用 CNB 镜像，构建时自动注入临时令牌作为 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://cnbprod.alpha.qq.com/<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` 参数             |
| build.by                       | `Array<String>` | `String` | 缓存构建过程中依赖的文件列表                          |
| build.versionBy                | `Array<String>` | `String` | 用于版本控制，支持传入文件夹路径                      |
| build.buildArgs                | `Object`                    | 在 build 时插入额外的构建参数                         |
| build.ignoreBuildArgsInVersion | `Boolean`                   | 版本计算是否忽略 buildArgs                          |
| build.sync                     | `String`                    | 是否等待 `docker push` 成功后继续；默认 `false`       |

* **build.target**：可以选择性地构建 Dockerfile 中的特定阶段
* **build.versionBy**：支持直接传入文件夹路径
* **build.sync**：是否等待 `docker push` 成功后才继续

::: warning 注意
未出现在 `by` 列表中的文件，在构建镜像时不会被 COPY 进镜像（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: verify
          script: ls node_modules
```

##### 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` |

* **path**：支持绝对路径（`/` 开头）或相对路径（`./` 开头），相对于工作区

::: warning 数据安全保障
同一流水线中，**不允许声明存在包含关系的卷路径**（不管 `group` 是否相同），
确保不同数据卷的数据在存储、读写时相互独立、互不干扰。
例如 `/a` 与 `/a/b/c` 存在路径包含关系，会导致两个数据卷的数据无法隔离。

* 一个路径是另一个路径的**前缀/后代**（如 `/a` 与 `/a/b/c`）→ 不允许
* 路径**完全相同**（如不同 `group` 下的 `/a` 与 `/a`）→ 不允许
* 前缀相似但不包含（如 `/aa` 与 `/a/b`）→ 允许
* 不同 `group` 之间同样适用该限制
  :::

::: warning 数量限制
同一流水线中，最多可声明 **32** 个数据卷。
请按需合理声明，超出数量上限时配置校验将无法通过。
:::

**数据卷类型**：

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

###### copy-on-write

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

`copy-on-write` 机制允许多个构建共享同一份缓存副本，仅在写入时才创建私有副本，从而避免并发读写冲突，提升缓存效率。

###### data

用于在容器间共享数据。

与直接挂载构建节点目录不同：当指定目录在容器中已存在时，
系统会先将容器中的内容复制到数据卷，再用数据卷挂载到其他容器，
而非直接覆盖容器目录。

###### 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
          # PR 场景使用 main 缓存，只读不更新
          # - main:/root/.gradle:copy-on-write-read-only
          # 容器内已有路径，自动复制到临时数据卷
          # - /go-app
      stages:
        - stage1
        - stage2
```

> PR 事件可改用 `copy-on-write-read-only` 类型使缓存只读，避免并发写入冲突。详见上方[数据卷类型](#docker-volumes)章节。

**示例 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`

指定是否拉取代码。

##### git.submodules

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

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

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

::: warning 公开仓库的子模块权限限制
拉取子模块使用的是具备代码读取权限的临时凭证，且遵循「公开仓库不能访问私有仓库资源」的原则：

* **公开仓库**的流水线只能拉取**公开仓库**的子模块，无法拉取指向**私有仓库**的子模块；
* **私有仓库**的流水线可以拉取触发者有权访问的其他仓库的子模块。

如果公开仓库依赖私有子模块，建议将主仓库改为私有，或不通过子模块方式引用私有仓库代码。
:::

###### 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"
```

> 对象形式也可按需配置（例如 `submodules: { enable: true, remote: true }`），详见上方参数说明。

##### 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"
```

> 对象形式（例如 `lfs: { enable: true }`）也可按需配置。

###### 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` 构建多种架构/平台的镜像，默认支持多架构构建能力，直接使用 `docker buildx` 无需额外配置：

```yaml title=".cnb.yml"
main:
  push:
    - services:
        - docker
      env:
        IMAGE_TAG: ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:latest
      stages:
        - 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/arm64,linux/arm/v7 --push .
```

> **注意**：一般情况下**无需**开启 `rootlessBuildkitd`，已默认支持 Docker 多架构构建。

```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/arm64 --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://cnbprod.alpha.qq.com/<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://cnbprod.alpha.qq.com/<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 后发布正式环境**。

**main 分支 push 时**——标记为 `MAIN` 与 `PREVIEW`：

```yaml title=".cnb.yml"
main:
  push:
    - label:
        # main 分支的常规流水线
        type:
          - MAIN
          - 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 时**——标记为 `RELEASE`：

```yaml title=".cnb.yml"
$:
  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>`

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

**示例**：`stages` 中任一任务失败后，依次执行 `failStages` 中定义的任务：

```yaml title=".cnb.yml"
main:
  pull_request:
    - name: pr-check
      stages:
        - name: lint
          script: npm run lint
        - name: test
          script: npm run test
      failStages:
        - name: notify-failure
          script: echo "PR check failed"
```

***

#### endStages {#pipeline-endstages}

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

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

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

**示例**：流水线结束前，无论 `stages` 是否成功，都依次执行 `endStages` 中定义的收尾任务：

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - name: build
          script: npm run build
      endStages:
        - name: cleanup
          script: echo "pipeline end"
```

***

#### 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 限制
因为文件变更可能非常多，变更文件的统计限制为最多 \_\_ENV\_\_GIT\_DIFF\_STAT\_ENTRY\_LIMIT 个。

* 支持文件变更统计的事件，若统计结果为空，则视为 `ifModify` 未命中。
* 上述支持事件以外，不适合统计文件变更，会忽略 `ifModify` 检查。
* **新建分支的 `push` 事件没有「上一个 commit」作为对比基准**，无法统计文件变更，会忽略 `ifModify` 检查；
  对变更敏感的场景建议改用 `commit.add` 等可明确统计文件变更的事件。
* 超出上限时，超限文件不参与 `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...

**示例**：测试失败后自动重试 2 次：

```yaml title=".cnb.yml"
main:
  push:
    - retry: 2
      stages:
        - name: test
          script: npm run test
```

***

#### allowFailure {#pipeline-allowfailure}

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

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

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

**示例**：允许当前流水线失败，失败时不把失败状态上报到 CNB：

```yaml title=".cnb.yml"
main:
  push:
    - allowFailure: true
      stages:
        - name: 通知
          script: echo 可选通知
```

***

#### 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)

**示例**：开启沙箱模式后执行任务：

```yaml title=".cnb.yml"
main:
  push:
    - sandbox: true
      stages:
        - name: echo
          script: echo 沙箱内执行
```

## 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`，系统会自动注入一个具备制品读取权限的临时令牌
用于拉取镜像。

***

#### 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 脚本命令。未指定 `image` 时在流水线容器中执行，
指定 `image` 时在独立容器中执行。
参考[任务间文件共享](./script-vs-plugin.md#file-sharing)了解目录共享机制。

**参数说明**：

| 参数     | 类型                          | 说明                                   |
| ------ | --------------------------- | ------------------------------------ |
| 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 镜像，也可称为镜像任务。插件任务在独立容器中执行。
参考[任务间文件共享](./script-vs-plugin.md#file-sharing)了解目录共享机制。

**特点**：

* 执行环境灵活
* 易于分享和复用
* 可跨 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` | 从文件加载插件任务参数                                |

* **settings**：按插件文档填写
* **settingsFrom**：指定本地或 Git 仓库文件路径，加载为插件任务参数

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

**with imports**：

```yaml
- name: npm publish
  image: plugins/npm
  imports: https://cnbprod.alpha.qq.com/<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://cnbprod.alpha.qq.com/<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`

设置单个任务的超时时间和无输出超时时间，
默认单个任务的超时时间为 2 小时，最大不能超过 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` 可能使用不同的解释器。
:::

**多行脚本写法**：

使用 YAML 的 `|` 多行语法可以编写多条命令：

```yaml
- name: build
  script: |
    npm install
    npm run build
    npm run test
```

当单条命令过长需要折行时，可配合 Shell 的 `\` 续行符：

```yaml
- name: deploy
  script: |
    aws cloudfront create-invalidation \
      --distribution-id ${DistributionId} \
      --paths "/*"
```

::: tip 区别

* **多条独立命令**：只需 `|`，每行一条命令
* **单条长命令折行**：需要 `|` 和 `\` 配合，`\` 告诉 Shell 下一行是当前命令的延续
  :::

***

#### 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`，系统会自动注入一个具备制品读取权限的临时令牌
用于拉取镜像。

***

#### 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   | 任务成功，中断当前流水线  |
| 其他数字 | 任务失败，中断当前流水线  |

* **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://cnbprod.alpha.qq.com/<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://cnbprod.alpha.qq.com/<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，但最多不能超过 64 个配置文件
2. include 的本地文件路径相对于项目根目录
3. 不支持引用 git submodule 中的文件
4. **不支持**跨文件使用 YAML 锚点 (`&`, `*`)
   :::
