- 数据存储
- 后端
【免费下载链接】opendal
Apache OpenDAL: One Layer, All Storage.
Apache OpenDAL 通过一套每周自动化的发布流水线,将"源码候选生成 → 签名上传 ATR → 社区投票 → 自动发布 → 版本同步"全流程收敛为可重复执行的工程实践。本文以仓库中的 weekly.md 为骨架,结合.github/workflows下的实际工作流与dev/src/release、scripts中的发布工具源码,完整还原这套"周度源码发布(Weekly source releases)"生命周期的设计原理、操作步骤与故障恢复方法。读完本文,你将掌握 OpenDAL 发布管理者(Release Manager,下称 RM)如何借助 ATR(Apache Trusted Releases)与 GitHub Actions 完成从周四切割到周一发布的自动化流程,并能在投票失败、构建缺失、发布中断等场景下准确干预。
工作流总览:周四切割、周一发布的目标与边界
周度发布由定时任务驱动:weekly_release.yml 中定义了schedule: cron '0 0 * * 4',即每周四 00:00 UTC触发。其基本流程是:
- 从周四 00:00 UTC 的
main分支切割点准备一条发布分支; - 调用 release-compose.yml 构建、签名并把候选上传到 ATR;
- RM 依照 手动发布流程 验证候选,并在 ATR 中开启自动决议 + 自动发布的投票;
- 每小时同步任务完成发布,并开启版本同步(version-sync)PR。
两个关键设计约束贯穿始终:
- 准备阶段不创建版本 PR、不需要手工合并:版本号由脚本根据已合并 PR 的破坏性变更声明直接计算并提交在候选分支上;
- 周一只是目标、不是强制期限:72 小时投票期从 RM 开启投票那一刻起算,而不是从准备开始起算。定时任务延迟、投票开启晚、票数不足都可能导致发布推迟。
从源码看,weekly_release.yml由prepare、compose、builds、notify四个 job 组成,prepare产出候选 SHA 与 RC 版本号,compose以workflow_call方式复用release-compose.yml,builds显式派发各语言绑定与文档的 RC 构建,notify在 GitHub Discussions 发布候选说明。整条链路的权限模型是"最小化":默认contents: read,仅在实际需要写引用、开 PR、写 Discussions 的 job 上按需放大。
候选准备(Preparation):源提交选择与版本决策
源提交选择:以 push 到达时间而非提交日期为准
定时运行会选择截止点(cutoff)之前最后一条被记录的 main push作为发布源提交。这一点在weekly_release.yml的preparejob 中有明确实现:脚本通过 GitHub API 查询weekly_release.yml自身在main上的 push 事件运行记录,再用max_by(.created_at, .id)选取created_at <= cutoff的最新一条并取其head_sha。
这条逻辑背后有两个必须理解的细节:
- push 运行时间戳记录的是提交到达 main 的时间,不是提交作者日期,因此不要用
git commit日期推断是否落在截止点内; - 必须保留这段运行历史直到准备完成:首次启用周度流程的那一周,需要有一条截止点之前被记录的 push,否则脚本会以
no push recorded before cutoff报错。
手动派发(workflow_dispatch)则走完全不同的路径:不要求历史 push,直接使用派发时选定的分支提交——即使运行排队期间分支继续前进,仍以派发时的 commit 为准。
版本决策:破坏性变更驱动,周度补丁兜底
准备阶段会读取上一次已发布源码提交与固定截止点之间合并进 main 的所有 PR,逐条校验其可选的破坏性变更声明,然后为每个受影响的包调用update-version --patch --breaking <package>。这一逻辑落在 scripts/release_impact.py 的prepare()与 dev/src/release/mod.rs 的update_version()中,版本计算的核心规则如下:
| 场景 | 版本处理 |
|---|---|
| 兼容 PR(无破坏性声明) | 维持正常补丁策略,不附加任何发布元数据 |
声明破坏性变更的0.x包 | 不兼容增量 =minor + 1(如0.58.x→0.59.0) |
声明破坏性变更的稳定1.x+包 | 不兼容增量 =major + 1(如1.2.3→2.0.0) |
0.0.x包 | 遵循 Cargo 的补丁兼容边界,patch + 1 |
| 多个破坏性 PR 命中同一包 | 只递增一次(幂等) |
| 配置中已更高的版本 / 新包 | 保留配置值,不做覆盖 |
实现细节可对照 bump.rs 的next_incompatible_version():major != 0走major+1,minor != 0走minor+1,否则走patch+1,与文档规则完全一致。prepare_versions()的单元测试(package.rs)还验证了"同一发布范围重复准备不会重复消耗补丁号"以及"破坏性声明对未知包直接报错"的行为。
两条容易被忽略但影响全局的规则:
- 公共依赖兼容性变化会连带提升受影响的集成包版本,但core 的破坏性变更并不会自动要求绑定包(bindings)做不兼容版本提升——绑定只要公共行为不变,就只需正常补丁。集成包(如
integrations/object_store、integrations/parquet)在 package.rs 中声明了public_compat_dependencies,bump.rs的prepare_public_dependencies()会检测其公共依赖跨过 Cargo 兼容线后强制提升集成版本; - 现有清单与依赖更新代码(
update_cargo_version、update_maven_version、update_nodejs_version、update_dotnet_version等,见 package.rs)会应用最终版本计划,各语言包按各自的清单格式(Cargo.toml、pom.xml、package.json、csproj)写入新版本。
.release/plan.json:冻结版本计划的单一快照
候选提交会写入.release/plan.json,包含baseline(基线版本)、source SHA、PR 声明(含破坏性变更与迁移说明)、版本决策。这个文件是整个发布周期的关键状态载体:
- 候选 Discussions 与最终 release notes 读取的是这份快照,而不是 PR 的实时描述——因此准备之后的讨论不会因 PR 描述被修改而失真;
- 恢复下游 job 时候选保持不变;如果声明有误,准备另一个 RC(而不是在同一候选上修补)可以收集修正后的声明;
- 下一个周度范围从已发布 plan 的 source SHA 开始,与 version-sync PR 是否已合并无关;
- 对于没有 plan 的旧版本,起始点取最终 tag 与截止点的 merge base;
- Draft 与 prerelease 状态的 GitHub Release 不会推进 baseline;
- 如果某个 commit 关联不到唯一 PR,或破坏性声明格式错误,准备会立即中止并指出受影响的 commit/PR。
声明破坏性变更(Declaring breaking changes)
这是版本决策的输入契约,全部围绕 PR 的可选元数据展开:
- 兼容 PR:PR 模板中的Breaking changes一节留空即可,无需列出包名或声明
none; - 破坏性 PR:必须同时满足两点——添加
breaking-changes标签,并填写模板中的声明,格式如下:
# Breaking changes Affected packages: core, bindings/java Migration: - Replace `old_api()` with `new_api()`.填写要求:
Affected packages使用逗号分隔,包名必须来自 dev/src/release/package.rs 中的发布清单(core、integrations 与各 bindings);- 即使绑定包源文件没有变化,只要公共行为发生变化,也必须列入;
Migration必须给出迁移方式或已删除功能的替代方案。
CI 侧由 ci_release_impact.yml 的Breaking change declarationjob 在 PR 变更及标签/描述编辑时提供反馈,运行python3 scripts/release_impact.py check。注意它不是强制合并检查(required check),真正的强制校验发生在每周准备阶段选择版本之前。校验逻辑见 release_impact.py:它会剥离 PR 正文中的注释与代码块、定位 "Breaking changes" 小节、检查标签与内容的一致性、校验受影响包名合法性并拒绝none/todo/tbd之类的占位迁移文本。
对审查者(reviewer)而言,API 变更、行为变更、默认值变更和运行时依赖变更都属于需要识别的破坏性变化——这套契约不会自动检测未标记的破坏性变更。因此在第一次启用周度流程前,需要人工复核所有未发布的破坏性 PR,补齐缺失的标签与迁移声明;已经发布的历史 PR 无需回填。
候选引用(Candidate refs)与 RC 编号
每次准备尝试会把版本、依赖与 changelog 更新提交到releases/<version>-rc.N分支,例如releases/0.59.3-rc.1,并在同一 commit 上原子地推送该分支和轻量标签v0.59.3-rc.1。之后把 SHA 传给 compose——compose 负责签名源码归档,而 RC 标签本身不签名。
关键约束:
- RC 编号取自已抓取标签中第一个未使用的正整数,包括手工准备的候选,因此保留 RC 标签可防止分支清理后编号被复用;
- 若推送发生冲突(conflicting push)则整体失败,绝不会替换已有标签;正确修复准备失败后再重跑;
- 准备新候选不会取消已有投票,也不会替换已批准的产物。RM 必须在开启新一轮投票前解决既有投票或待发布的候选;废弃分支与 ATR 草稿可以单独清理。
生命周期配置(Configuration):首次启用前的五项准备
以下配置大多属于外部仓库 / ATR / Nexus 设置,合并工作流代码并不会自动生效,且绝不能仅为了测试凭据而发布一个版本。首次启用前需要完成:
- GPG:复用
GPG_SECRET_KEY与SOURCE_SIGNING_FINGERPRINT,用于源码归档与签名的最终 tag;源码签名同时接受 schedule 与手动 dispatch 事件; - ATR compose 调用方:注册 .github/workflows/weekly_release.yml 为 ATR 的 compose caller,同时注册 .github/workflows/release-compose.yml 以支持直接 compose 运行;
- ATR finish 调用方:注册 release_lifecycle.yml 与 release_publish.yml 为 ATR 的finishcallers。每小时运行会直接调用发布工作流,从而保留调用方身份供 OIDC 使用——而不是以
github-actions[bot]身份去 dispatch 发布; - 账号权限:定时任务的 owner 与手动派发者必须拥有关联了 ASF 的 GitHub 账号并具备 OpenDAL 所需权限;RM 应维护 cron 表达式,因为它决定了定时任务的执行者身份;
- Nexus 凭据:现有
NEXUS_STAGE_DEPLOYER_USER/NEXUS_STAGE_DEPLOYER_PW必须既能上传 staging,又能提升 OpenDAL 已关闭的 staging 仓库。
此外还需允许 GitHub Actions 创建 PR、写入发布引用与 Discussions;既有的包发布者凭据与 release 环境规则继续适用。
构建派发与候选通知(Builds and preparation notice)
由于GitHub 不会为GITHUB_TOKEN创建的 tag 触发下游 push 工作流,推送 RC 标签后,weekly_release.yml的buildsjob 会显式 dispatch既有的 tag 构建、绑定检查与文档工作流。派发时保留 RC 语义:
| 包 | RC 阶段行为 |
|---|---|
| Java | 上传到 Nexus staging |
| Python | 使用 TestPyPI |
| NodeJS | 仅执行发布 dry run |
| Ruby / .NET | 保留产物 |
| Rust | 不发布 |
| 文档 | 保留 RC staging,不部署到 nightlies |
禁用的工作流会被跳过并记录在运行摘要中;重试会跳过已经为该候选 commit 派发过的工作流,失败的下游 job 应从其自身运行中重跑。
ATR 上传与派发成功后,notifyjob 会在 General Discussions 创建Release candidate: <RC>作为共享发布入口,包含:精确 commit、ATR revision、检查项、下载与构建链接、RM 操作清单、CLI 命令、最终公告草稿、包版本决策以及破坏性变更迁移说明。上传与派发成功并不代表构建或 ATR 检查通过——RM 必须在开启投票前独立验证候选。
投票与社区通知(Vote and community notification)
投票操作既可用官方 ATR CLI,也可用 ATR 的浏览器控件。候选 Discussion 会把真实 RC 与 revision 代入以下命令:
atr check status opendal 0.59.3-rc.1 00001 uv run --python 3.13 --with "apache-trusted-releases @ git+https://github.com/apache/tooling-releases-client" python - <<'PYTHON' from atrclient import api from atrclient.models.api import VoteStartArgs task = api.vote_start(VoteStartArgs( project="opendal", version="0.59.3-rc.1", revision="00001", email_to="dev@opendal.apache.org", automatic_resolve_when_finished=True, automatic_publish_when_resolved=True, notify_when_finished=True, )).task print(task.model_dump_json(indent=2)) assert task.task_args["automatic_resolve_when_finished"] is True assert task.task_args["automatic_publish_when_resolved"] is True PYTHON注意事项(这是文档反复强调的坑):
- Python 脚本运行的是官方 ATR 客户端,认证配置与
atr相同,并在返回的 task 中校验两个自动化标志;这段脚本同样出现在 release_lifecycle.py 生成的候选通知正文中; atr vote start --auto-publish只开启发布、不开启自动决议;通用atr api post发送的是字符串而非布尔值,无法满足要求;- 如果标志校验失败,去 ATR 检查该 task,不要重复执行 start 命令;要重新读取 task 用
atr api get /task/get/TASK_ID,对已活跃的投票绝不重复开启。
投票结束时 ATR 检查其 Trusted Vote 选票:投票通过则自动决议并发布已批准的源码文件;未通过则保持未决议状态等待人工跟进,且后续补投的票不保证会触发另一次自动尝试。GitHub 与邮件列表的讨论回复不计入ATR Trusted Vote 选票,需按 ATR 指引投票。人工恢复流程:
atr vote tabulate opendal 0.59.3-rc.1 atr vote resolve opendal 0.59.3-rc.1 passedCLI 不可用时用 ATR 浏览器控件取消投票或查看计票。Agent 可在 RM 授权投票动作后使用同一 CLI,凭据放在 CLI 的隐藏提示中;CI 则只读 ATR 的 JSON API,并通过其 trusted-publisher 公告端点工作。
每小时同步任务 release_lifecycle.yml 在每小时的第 17 分钟运行(GitHub 可能延迟定时任务)。它保持候选 Discussion 的开帖不变、以评论追加状态变化,每个 ATR 投票轮次至多提醒一次;状态未变化则不产生新评论。同步更快的手动命令:
gh workflow run release_lifecycle.yml --repo apache/opendal --ref mainGitHub 同步永远不会开启或决议投票——这些动作归 ATR 所有;失败或取消的投票不可能触发发布;已开始的投票保留其原始设置,未来候选修改命令不会给已开始的投票开启自动决议。
恢复既有候选(Recovering an existing candidate)
在假设自动派发可用之前,先检查周度运行的 revision 与各 job:工作流变更必须存在于该运行使用的 revision 中——合并修复不会回填旧候选。列出既有 tag 的运行时要不过滤push事件,因为自动派发产生的是workflow_dispatch事件:
gh run list --repo apache/opendal --branch "v${release_candidate_version}" \ --limit 100 --json name,event,headSha,status,conclusion,databaseId,url随后:核对每个运行的 head SHA 与候选 commit;失败的下游 job 从既有运行重跑;若某工作流没有运行,则针对同一 RC tag 派发。例如恢复缺失的 Python 发布构建:
gh workflow run release_python.yml --repo apache/opendal \ --ref "v${release_candidate_version}"派发前先读取 RC tag 上的工作流版本——它必须在该 tag 处支持workflow_dispatch,而不只是在 main 上。以周度buildsjob 的工作流列表与输入为权威参照:
- NodeJS 使用
nodejs-publish=false与nodejs-publish-dry-run=false(RC tag 本身会启用其 dry run); - Docs 以 RC tag 作为
release_version,并带deploy-nightlies=false; - 缺少必需输入或派发触发器的旧 tag 需要单独做恢复决策,不要移动它。
派发成功只代表被接受,仍需找到对应运行并跟进到完成;候选通知不能替代对全部必需构建、registry staging 与 ATR 检查的核验。compose 权限失败时,修正 ATR 的允许调用方/actor 配置,并在原运行已完成输出仍可用时重跑失败 job、复用既有签名产物——重跑准备或启动新周度运行只会创建新候选,而不是恢复旧候选。
投票通过之后(After the vote passes)
对新的releases/<version>-rc.N候选,每小时工作流一旦发现 ATR 报告决议通过的投票,就会调用 release_publish.yml,其核心执行体是python3 scripts/release_lifecycle.py publish --rc "$RC"。完整流程共五步:
- 创建最终引用:校验 RC 分支与 tag 指向同一 commit,在该 commit 上创建保留的
releases/<version>分支与签名的v<version>tag。已存在的最终引用必须一致且绝不移动。这里不使用 ATR OIDC 的commit_hash,因为上传工作流在生成 RC 之前就已从 main 启动; - 创建 GitHub Release 并公告:创建 GitHub Release,并请 ATR 向
announce@apache.org发送公告;ATR 会检查源码发布与下载传播情况。审核后的公告发布在 GitHub Announcements,仓库会将其镜像到 dev@opendal.apache.org; - 开启版本同步 PR:从最新 main 打开草稿 version-sync PR。
update-version --baseline v<version> --sync按包取 main 与已发布版本中的较高者,保留新包与开发中的变更,并重新生成依赖、lockfiles 与 changelog 条目。工作流不会合并 release 分支、也不会自动合并 PR;由于GITHUB_TOKEN不会触发 PR CI,维护者需关闭再重开审核后的 PR(或推送一次更新)来启动检查; - 跟进可选的语言分发:在最终 tag 上派发既有的 Rust、Python、Node.js、Ruby、.NET、Dart 与 Docs 工作流(跳过禁用的),并提升已批准的 Java Nexus staging 仓库。Go 属于独立仓库。待处理或失败的包 job 不会阻塞 GitHub Release、公告或 version-sync PR;
- 汇报与清理:在候选 Discussion 中汇报可选分发的进度。分发需要跟进时保留 RC 分支,便于每小时发现恢复;完成后删除同版本的 RC 分支——每次删除都要求其 RC tag 仍保留该 commit,且被批准的分支最后删除,保证部分清理仍可被发现。最终分支、最终 tag 与 RC 标签保留。
每个小时运行会检查外部完成记录并恢复未完成工作。可选包失败仍可见于其自身 Actions 运行与候选通知——源码发布此时已成功,只需重跑失败的包 job,再等待下一个小时周期或显式恢复:
gh workflow run release_publish.yml --repo apache/opendal --ref main \ -f rc=0.59.3-rc.1如果 version-sync PR 被关闭而未合并,恢复前先重开;如果分支推送成功但 PR 创建失败,重试会复用该分支。一次成功的发布运行仍可能是在等待包分发——需检查候选 Discussion 与下游结果确认完成。包工作流成功是自动门禁,RM 也可以按发布流程用每个包的版本在 registry 中核验传播情况。
与手动流程的衔接:旧式候选的边界
这套生命周期只发现新的releases/分支命名空间。既有的release-candidates/weekly-*候选(包括 0.59.2)继续走 手动官方发布流程,不要重命名或迁移它们来纳入自动化。无论新旧,源码归档在整个流程中始终是 ATR 批准的 revision;手动 SVN 流程仅适用于 SVN staging 的源码。
另外值得说明的是:对于生命周期之外的候选,手动发布流程 仍然是权威操作手册——周度文档与它通过weekly.md#declaring-breaking-changes、weekly.md#after-the-vote-passes等锚点互相引用,二者共同构成 OpenDAL 发布体系的完整文档面。本文涉及的候选通知模板、版本计划渲染与发布恢复逻辑,均可在 scripts/release_lifecycle.py 与 scripts/release_impact.py 中直接核对,适合作为自动化发布链路二次开发与故障排查的起点。
- 数据存储
- 后端
【免费下载链接】opendal
Apache OpenDAL: One Layer, All Storage.
相关推荐
Apache OpenDAL Release Candidate(RC)发布全流程实战指南:从每周 ATR 候选到 Nexus 投票就绪
Apache OpenDAL Release Candidate(RC)发布全流程实战指南:从每周 ATR 候选到 Nexus 投票就绪 Apache Open
数据存储后端Apache OpenDAL 发布管理实操指南:从 RC 候选、ATR 投票到官方发布全流程
Apache OpenDAL 发布管理实操指南:从 RC 候选、ATR 投票到官方发布全流程 Apache OpenDAL 的每一次版本发布都不是普通的 CI
数据存储后端Apache OpenDAL 发布投票(Release Vote)完整实操指南:ATR 自动化投票与手动 SVN 投票路径
Apache OpenDAL 发布投票(Release Vote)完整实操指南:ATR 自动化投票与手动 SVN 投票路径 导读 Apache OpenDAL
数据存储后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考