Artifacts 与自定义 Action 发布:从上传下载到市场分发

系统讲解 GitHub Actions Artifacts 的生命周期管理与自定义 Action 的完整开发流程,涵盖 upload/download-artifact 深度用法、retention 策略、跨 workflow 传递、JavaScript/Composite/Docker 三类 Action 开发、action.yml 元数据规范以及发布到 Marketplace 与语义化版本管理。

Artifacts 是 workflow 之间传递构建产物的官方通道,自定义 Action 则是把重复逻辑封装成可复用单元的核心手段。本文前半部分深入 actions/upload-artifact 与 actions/download-artifact 的 v4 语法、retention 策略与跨 workflow 传递方案;后半部分完整覆盖 JavaScript / Composite / Docker 三类自定义 Action 的开发、action.yml 元数据规范,以及从本地仓库到 Marketplace 的版本发布流程。


一、Artifact 生命周期与基础用法

1.1 什么是 Artifact

Artifact 是 GitHub Actions 提供的文件存储服务,用于在 job 之间传递构建产物(编译结果、测试报告、安装包)。它本质上是绑定在 workflow run 上的临时存储,run 结束后进入 retention 倒计时。

┌────────────┐   upload   ┌──────────────┐   download   ┌────────────┐
│  Build Job │ ─────────► │  Artifact    │ ◄─────────── │  Test Job  │
│  (compile) │            │  存储(仓库级) │              │  (run)     │
└────────────┘            └──────────────┘              └────────────┘

1.2 upload-artifact v4 深度语法

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: |
          mkdir -p dist
          echo "hello" > dist/app.txt
          tar -czf dist.tar.gz dist

      - name: Upload single file
        uses: actions/upload-artifact@v4
        with:
          name: app-bundle
          path: dist.tar.gz
          if-no-files-found: error        # error | warn | ignore
          compression-level: 6            # 0-9,默认 6
          retention-days: 30              # 1-90,默认 90

v4 的关键变化:path 支持多路径与 glob 模式,if-no-files-found 默认 error,compression-level 可调(GZip 0-9,更高的压缩降低存储成本但增加 CPU)。

1.3 download-artifact v4 语法

jobs:
  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      # 下载单个 artifact 到指定目录
      - uses: actions/download-artifact@v4
        with:
          name: app-bundle
          path: ./artifacts

      # v4 支持按 glob 模式批量下载
      - uses: actions/download-artifact@v4
        with:
          pattern: 'coverage-*'
          path: ./coverage
          merge-multiple: true            # 合并到同一目录

一句话:v4 中 download-artifact 不再有「不指定 name 就下载全部」的默认行为,必须显式传 name、pattern 或 github-token 枚举全部。


二、retention 策略与管理

2.1 保留期模型

层级默认保留期说明
仓库设置90 天全局默认,可在 Settings → Actions → General 调整
工作流级别90 天retention-days 覆盖,范围 1-90(企业版可达 400)
Enterprise 全局1-400 天组织级策略统一管控

2.2 手动与 API 清理

# gh CLI 列出 run 的 artifacts
gh api "/repos/OWNER/REPO/actions/artifacts?per_page=100" \
  --jq '.artifacts[] | "\(.name) \(.created_at) \(.expires_at)"'

# 删除指定 artifact
gh api -X DELETE "/repos/OWNER/REPO/actions/artifacts/{artifact_id}"

# 删除过期 artifact 的定时任务示例
gh workflow run cleanup-artifacts.yml

2.3 保留期决策表

产物类型建议保留期理由
测试报告/覆盖率7 天足够排查回归,避免堆积
安装包/二进制90 天关联 Release,可作为备份
敏感文件(密钥/配置)0(不传)尽量不入 Artifact,防泄露
合规审计所需400 天(企业)满足内部审计要求

三、Artifact 跨 workflow 传递

3.1 问题:Artifact 默认只在 run 内可见

不同 workflow 之间默认无法直接读取彼此的 Artifact。跨 workflow 传递的标准方案是 workflow_run 事件——下游 workflow 在上游 workflow 完成后触发,并通过 API 下载其 Artifact。

3.2 上游:上传 Artifact

# .github/workflows/build.yml
name: Build
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: echo "release" > binary.tar.gz
      - uses: actions/upload-artifact@v4
        with:
          name: release-artifact
          path: binary.tar.gz
          retention-days: 30

3.3 下游:workflow_run 触发并下载

# .github/workflows/deploy.yml
name: Deploy
on:
  workflow_run:
    workflows: ["Build"]
    types: [completed]

jobs:
  deploy:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    permissions:
      actions: read                      # 需要读取 Artifact
      contents: read
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: release-artifact
          github-token: ${{ secrets.GITHUB_TOKEN }}
          run-id: ${{ github.event.workflow_run.id }}
          path: ./release
      - run: ./deploy.sh ./release/binary.tar.gz
方案适用场景限制
同 run 内 needs + Artifactjob 间传递无跨 run 能力
workflow_runworkflow 间传递下游无法在上游中传参
可复用工作流 workflow_call参数化编排需在同一调用树内
GitHub Packages / Release长期存储带版本语义,非临时产物

四、自定义 Action 的三种类型

4.1 选型对比

类型运行方式适用场景依赖推荐度
JavaScriptNode.js 运行时需要处理复杂逻辑、调用 GitHub API@actions/core、@actions/github★★★★★
Composite复用 shell/step组合现有 steps,无代码逻辑无,仅 YAML★★★★★
Docker容器内执行语言无关、环境固定Dockerfile★★★★☆

4.2 三种类型的 action.yml 骨架

# JavaScript Action
name: "My JS Action"
description: "在 Node 运行时中执行的 Action"
inputs:
  who-to-greet:
    description: "要问候的人"
    required: true
    default: "World"
outputs:
  time:
    description: "问候时间"
runs:
  using: node20
  main: dist/index.js
# Composite Action
name: "My Composite Action"
description: "组合多个 steps 的 Action"
inputs:
  target:
    required: true
outputs:
  result:
    description: "执行结果"
runs:
  using: composite
  steps:
    - run: echo "准备阶段 ${{ inputs.target }}"
      shell: bash
    - uses: actions/checkout@v4
    - run: echo "完成"
      shell: bash
# Docker Action
name: "My Docker Action"
description: "在容器中执行的 Action"
inputs:
  command:
    required: true
runs:
  using: docker
  image: Dockerfile
  args:
    - ${{ inputs.command }}

五、JavaScript Action 开发实战

5.1 项目结构与依赖

JavaScript Action 需要打包成单文件(通常用 @vercel/ncc),因为运行时只会执行 main 指向的入口文件,不会安装 node_modules:

my-action/
├── action.yml
├── package.json
├── src/
│   └── main.js
└── dist/
    └── index.js        # ncc 打包产物

package.json 关键字段:

{
  "name": "my-action",
  "main": "dist/index.js",
  "scripts": {
    "build": "ncc build src/main.js -o dist --source-map"
  },
  "dependencies": {
    "@actions/core": "^1.10.0",
    "@actions/github": "^6.0.0"
  }
}

5.2 核心 API 用法

const core = require('@actions/core');
const github = require('@actions/github');

try {
  // 读取输入
  const whoToGreet = core.getInput('who-to-greet');
  console.log(`Hello ${whoToGreet}!`);

  // 获取触发上下文(repo、sha、event)
  const ctx = github.context;
  console.log(`repo: ${ctx.repo.owner}/${ctx.repo.repo}`);

  // 设置输出
  core.setOutput('time', new Date().toTimeString());

  // 失败:设置 job 失败并终止
  // core.setFailed('遇到错误');
} catch (error) {
  core.setFailed(error.message);
}

5.3 与 GitHub API 交互

const core = require('@actions/core');
const github = require('@actions/github');

const token = core.getInput('token', { required: true });
const octokit = github.getOctokit(token);

async function main() {
  const { owner, repo } = github.context.repo;
  const pr = github.context.payload.pull_request;
  if (!pr) return;

  // 在 PR 上创建评论
  await octokit.rest.issues.createComment({
    owner, repo,
    issue_number: pr.number,
    body: '构建通过 ✅',
  });
}
main().catch((e) => core.setFailed(e.message));

一句话:core.setOutput 与 core.setFailed 是 JavaScript Action 与 workflow 交互的唯二通道;输出可被后续 step 用 ${{ steps.<id>.outputs.<name> }} 引用。


六、Composite 与 Docker Action

6.1 Composite Action 的注意事项

Composite Action 本质是把多个 step 打包,但有几个硬性约束:

runs:
  using: composite
  steps:
    - name: 运行脚本
      run: |
        echo "${{ github.action_path }}"   # 只能在 composite 中使用
        ./scripts/setup.sh
      shell: bash                          # 每个 run 步骤必须显式声明 shell

    - name: 引用外层工作流目录
      run: |
        # composite 的工作目录是 Action 仓库,不是调用方仓库
        echo "使用 inputs 传入调用方路径: ${{ inputs.source-path }}"
      shell: bash
约束说明
shell 必填每个 run step 必须声明 shell
uses 受限制只能引用其他 action,不能引用 composite action
env 不继承composite 内定义的环境变量不泄漏到外层
输出需显式用 echo "name=value" >> $GITHUB_OUTPUT 声明
绝对路径默认 cwd 是 action 仓库,调用方文件需用 ${{ github.workspace }} 或 input 传入

6.2 Docker Action 的典型场景

当 Action 需要特定语言/工具链(如 Python 脚本、Golang 编译)时,Docker 是最干净的方式:

# Dockerfile
FROM python:3.12-slim
COPY entrypoint.py /entrypoint.py
ENTRYPOINT ["python", "/entrypoint.py"]
# action.yml
name: "Python Linter"
description: "在固定 Python 环境中运行 lint"
inputs:
  lint-path:
    description: "要检查的路径"
    required: true
runs:
  using: docker
  image: Dockerfile
  args:
    - ${{ inputs.lint-path }}

七、发布到 Marketplace 与版本管理

7.1 发布前置条件

  • Action 仓库必须是公开仓库
  • 必须包含 action.yml(合法元数据)
  • 仓库需添加 topics(如 github-actions、actions)便于被发现
  • 打 tag 后即可在 Marketplace 中搜索到

7.2 语义化版本 tag 策略

# 完整版本(不可变)
git tag v1.2.3

# 大版本移动 tag(可变,随补丁推进)
git tag -f v1
git tag -f v1.2

# 推送
git push origin v1.2.3 v1 v1.2 --force
用户引用方式风险推荐度
@v1.2.3完全固定,无漂移✅ 企业级首选
@v1随大版本内更新✅ 常用
@main随仓库最新代码变化❌ 不可复现
@<full-sha>精确锁定提交✅ 安全加固首选

7.3 Release 与 README

每次发版都创建 GitHub Release,并维护 README 的使用示例:

## 用法

```yaml
- uses: your-org/my-action@v1
  with:
    who-to-greet: "Octocat"

> **一句话**:自定义 Action 的版本承诺是「发布给他人用的契约」——用 `v1.2.3` 固定引用保证可复现,用 `v1` 浮动标签平衡便利,绝不让用户裸引用 `main`。

### 7.4 在 workflow 中测试自定义 Action

发布前应在本地仓库的 CI 中自测,并利用可复用工作流组织级共享:

```yaml
# 调用同仓库的本地 Action(用路径引用,不经市场)
jobs:
  test-local-action:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./
        with:
          who-to-greet: "本地测试"

八、安全与最佳实践

8.1 Artifact 安全

  • 敏感文件(.env、私钥)绝不上传为 Artifact——它可被组织内具 actions: read 权限者下载
  • 设置 retention-days 缩短敏感产物生命周期
  • fork 的 PR 默认无法访问上游 Secrets,但 Artifact 仍需审慎

8.2 自定义 Action 安全清单

  • action.yml 中 description 非空(Marketplace 强制)
  • 输入类型明确(string/boolean/number/choice)
  • JavaScript Action 打包后校验 dist/ 与源码一致
  • 依赖固定版本或 SHA,避免供应链投毒
  • 不将 GITHUB_TOKEN 写入日志
  • 发布前在真实仓库跑通冒烟测试

总结

Artifacts 与自定义 Action 构成了 GitHub Actions 复用体系的两块基石。

维度关键要点常见误区
Artifact 传递同 run 用 needs,跨 run 用 workflow_run忘记 permissions: actions: read
retention 策略默认 90 天,按产物类型缩短敏感文件也设长保留期
Action 类型JS 处理逻辑 / Composite 组合 steps / Docker 固定环境把简单 step 组合也写成 JS
版本管理v1.2.3 固定 + v1 浮动裸引用 main 导致不可复现
发布安全固定依赖 SHA、清理 dist、控制 Secrets忽略供应链与日志泄露

一句话:把「重复的 step 组合」做成 Composite Action,把「需要逻辑的步骤」做成 JavaScript Action,把「需要固定工具链的步骤」做成 Docker Action;再用语义化 tag 与安全加固完成从本地仓库到 Marketplace 的分发闭环。


延伸阅读:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「github-actions」更多文章

  1. 移动端 CI/CD:Flutter/iOS/Android 构建与签名
  2. 环境保护与部署门禁:环境规则、审批与 CD 流程
  3. 工作流安全加固与供应链防御