
# 流水线与 pipe

一条**流水线**就是一次运行。`.acahti/pipelines/` 中的每个 YAML 文件是这次运行中的一个**作业**，每个作业包含若干具名**步骤**。**pipe** 是官方提供的可复用步骤：写作 `pipe: <name>@v1`，输入放在 `with:` 下。

在执行节点启动作业之前，网关会先展开它：`pipe:` 变成对 `acahti-pipe <name>` 的调用，映射的密钥变成步骤环境变量，每个步骤都会拿到本次运行的 Acahti 身份：`ACAHTI_USER`、`ACAHTI_TOKEN`、`ACAHTI_ROOT_URL` 和 `ACAHTI_ORG`。这个身份是头提交作者的 Acahti 账号（找不到时退回到启动这次运行的账号）；步骤自己在 `environment:` 中设置的值优先。执行节点只会看到展开后的 YAML。

## 编写作业

步骤要么是带 `with:` 的 `pipe:`，要么是用于一次性操作的普通 `commands:`。`when`、`depends_on` 和 `labels` 原样传递。不要把辅助脚本复制进仓库，也不要写 `uses:`。

```yaml
steps:
  test:
    image: node:22
    commands:
      - npm ci
      - npm test
```

不同文件中的作业按 `depends_on` 排序，父作业在前。作业如果声明了 `concurrency:`，其 `group` 的作用范围是所在仓库，所以两个仓库中的 `group: deploy` 不会互相串行。不写 `concurrency` 则不受限制地运行。

在 `commands:` 中，用 `$VAR` 引用步骤环境变量。`${VAR}` 会在 shell 启动前插值，除非它是作业上下文（如 `${CI_COMMIT_SHA}` 或 `${CI_COMMIT_TAG}`），否则为空。

通过 `git push`、标签或 PR 触发运行。Git 标签格式为 `vX.Y.Z`；镜像标签去掉 `v`（`${CI_COMMIT_TAG#v}`）。

## 密钥

流水线密钥是访问 Acahti 之外系统的凭据：镜像仓库、集群、对象存储。管理员在网页端（「管理 → 组织密钥」，或仓库的「密钥」页面）或通过 `secret_put` 添加。名称使用小写 `snake_case`。密钥值永远不会被列出或返回。

步骤需要主动声明使用哪些密钥。密钥名就是 pipe 需要的变量名时直接列出；名称不同时做映射：

```yaml
secrets:
  KUBECONFIG: kubeconfig_prod
  DOCKER_USERNAME: registry_username
  DOCKER_PASSWORD: registry_password
```

不要在 `with:` 中填写凭据文件的路径。Acahti 自带的 npm 和 PyPI 仓库源使用本次运行的身份，因此不要为它们创建令牌密钥。

## 官方 pipe

### docker-build

Build images with BuildKit and push them, or build cache-only for CI.

| Input | Required | Description |
|---|---|---|
| `images` | yes | One image per line. First field is the primary tag. Optional KEY=VAL: also extra tags (comma-separated), context (default .), file (Dockerfile), anything else is --build-arg. Job identity is written as BuildKit secrets id=npmrc and id=netrc (HTTP Basic from ACAHTI_USER / ACAHTI_TOKEN). Dockerfiles mount id=npmrc at /root/.npmrc and id=netrc at /root/.netrc. Default --network=host on buildx. CODEUP_NETRC, when set, is passed as id=codeup_netrc. |
| `push` |  | true (default) is buildx --push; false is --output=type=cacheonly (CI) |
| `network` |  | docker buildx --network (default host) |
| `builder` |  | buildx builder name (default acahti, docker-container + host network) |

### docker-gc

Bound shared BuildKit cache and report disk pressure. Runs every 15 minutes on the Runner.

### docker-login

Log in to a container registry for later steps in the job. Secret DOCKER_PASSWORD.

| Input | Required | Description |
|---|---|---|
| `registry` | yes | Registry hostname. An http:// prefix is the same as http: true. |
| `http` |  | true if the registry is HTTP/insecure (BuildKit). Default false (HTTPS). |
| `username` |  | Registry user; password is DOCKER_PASSWORD (or DOCKER_USERNAME + DOCKER_PASSWORD) |

### helm

Install or upgrade a Helm release. Secret KUBECONFIG holds the kubeconfig document.

| Input | Required | Description |
|---|---|---|
| `release` | yes |  |
| `namespace` | yes |  |
| `chart` |  | Chart path relative to the workspace (default chart) |
| `files` |  | Extra -f values files, one per line, relative to the workspace |
| `set` |  | Extra --set lines (foo=bar or --set foo=bar) |
| `timeout` |  | Helm --timeout (default 5m) |
| `take_ownership` |  | true to pass --take-ownership |
| `jump` |  | SSH host, reachable from the Runner, that runs helm when the cluster API is private |

### npm-publish-acahti

Build an npm package and publish it to this org's Acahti registry.

| Input | Required | Description |
|---|---|---|
| `path` | yes | Package directory (package.json). Installs, builds, packs, and PUTs to this Acahti org's npm. |
| `image` |  | Node image (default node:22-alpine). Private registry images need a prior docker-login. |

### oci-gc

Prune old tags from OCI repositories, keeping latest and the newest few.

| Input | Required | Description |
|---|---|---|
| `repos` | yes | OCI repositories to prune (no tag), one per line |
| `keep` |  | Non-latest tags to keep (default 2). latest is always kept. |

### oss-put

Upload files to Alibaba Cloud OSS. Secrets OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET.

| Input | Required | Description |
|---|---|---|
| `endpoint` | yes |  |
| `bucket` | yes |  |
| `prefix` | yes | Object key prefix (no oss://) |
| `files` | yes | Local files to upload, one per line (workspace-relative) |

### pypi-publish-acahti

Build a Python package and publish it to this org's Acahti registry.

| Input | Required | Description |
|---|---|---|
| `path` |  | Directory with pyproject.toml (default .). Builds and PUTs to this Acahti org's PyPI. |
| `image` |  | Python image (default python:3.12-slim-bookworm). uv is installed if missing. Private registry images need a prior docker-login. |

### uv

Run commands in a job-local Python environment with uv.

| Input | Required | Description |
|---|---|---|
| `project` |  | Directory with pyproject.toml (default .) |
| `run` | yes | One command per line; each is uv run --project <dir> -- <line> |

### wait-http

Wait until URLs stop answering 502, 503, 504, or nothing.

| Input | Required | Description |
|---|---|---|
| `urls` | yes | URLs to poll, newline or semicolon separated |
| `timeout` |  | Seconds to wait (default 180) |
| `user_agent` |  | User-Agent header for the probes |

## 示例

构建镜像但不推送的 CI：

```yaml
steps:
  build:
    pipe: docker-build@v1
    with:
      push: false
      images: |
        app:${CI_COMMIT_SHA}
```

构建、部署并清理旧标签的 CD，同一时间只运行一个部署：

```yaml
concurrency:
  limit: 1
  group: deploy
steps:
  login:
    pipe: docker-login@v1
    with:
      registry: registry.example.com
    secrets:
      DOCKER_USERNAME: registry_username
      DOCKER_PASSWORD: registry_password
  build:
    depends_on: [login]
    pipe: docker-build@v1
    with:
      images: |
        registry.example.com/app:${CI_COMMIT_SHA} also=registry.example.com/app:latest
  deploy:
    depends_on: [build]
    pipe: helm@v1
    with:
      release: app
      namespace: default
      files: |
        chart/values.yaml
      set: |
        image.tag=${CI_COMMIT_SHA}
    secrets:
      KUBECONFIG: kubeconfig_prod
  gc:
    depends_on: [deploy]
    pipe: oci-gc@v1
    with:
      repos: |
        registry.example.com/app
```

打标签时把包发布到组织的仓库源：

```yaml
when:
  event: tag
steps:
  publish:
    pipe: pypi-publish-acahti@v1
```

在名为 `cd` 的部署作业之后运行端到端测试：

```yaml
depends_on: [cd]
steps:
  e2e:
    pipe: uv@v1
    with:
      run: |
        argos run all --env bj-test --dash
```

该步骤使用本次运行的 `ACAHTI_ROOT_URL` 和 `ACAHTI_TOKEN` 运行，因此 `--dash` 无需密钥即可把运行结果推送到 [Argos](/docs/testing/)。当作业或其某个步骤的名称包含 `e2e` 或 `argos` 时，流水线页面会把 CLI 报告的运行（`argos <sid>`）链接为「Argos」。

## 把流水线关联到工作项

当流水线的提交信息、分支或 PR 标题提到某个待办或工单（`todo#123`、`ticket#45`、`todo-123`、`todo/123`、`ticket/45`、`T-123`）时，Acahti 会把这条流水线及其对应的 PR 关联到该工作项。PR 还会根据自身的标题、正文和头分支进行关联。之后，流水线会出现在该工作项[追踪](/docs/track/)流程的 Build 下，PR 出现在 Review 下。
