Errata 自动化更新工具:跨发行版包同步流水线
作者:杨显钊
版本:1.0
日期:2024年8月11日
标签:Errata、发行版同步、Anolis、RPM、Git、自动化
适合人群:Linux 发行版维护者、软件包管理工程师、DevOps
摘要
本文介绍了一套errata(勘误)自动化更新工具,用于解决上游 Linux 发行版(Anolis OS)与下游发行版之间的软件包同步问题。该工具通过 Gitee API 自动检测上游仓库更新,对比下游版本号,识别下游是否有定制修改,智能分类为"自动同步"“手动同步”"无需操作"三类,最终生成 Excel 报告并通过邮件通知团队。本文详细拆解了工具的整体架构、核心流程、关键技术实现,并附代码片段,为发行版维护者提供一套可复用的自动化同步方案。
一、背景与痛点
在操作系统发行版维护中,上游(upstream)更新同步到下游(downstream)是一个常见但繁琐的任务。
某 Linux 发行版基于 Anolis OS(龙蜥操作系统)构建。Anolis OS 在 Gitee 上维护着数千个源码仓库(src-anolis-os/*),当上游修复了 CVE 漏洞、发布了新特性,下游需要及时跟进。然而,下游对某些包有自己的定制修改,不能盲目覆盖,需要人工判断——这就需要一个自动化的 errata 同步工具。
本文介绍的工具正是为了解决这个痛点而生的:自动检测 Anolis OS 的包更新,对比下游版本,智能分类处理方式,生成 Excel 报告并邮件通知团队。
二、技术栈
| 类别 | 技术 | 用途 |
|---|---|---|
| 语言 | Python 3 | 核心调度、Excel 生成、邮件发送 |
| 脚本 | Bash | Git 操作、版本比较、RPM 构建命令 |
| 版本控制 | Git、Gitee API v5、Gerrit | 克隆仓库、检测更新、推送同步 |
| 构建系统 | Koji (RPM) | 重建源码包至目标标签 |
| RPM 工具 | rpm2cpio、cpio | 提取源码包 |
| Excel | xlwt、xlrd、xlutils | 生成带格式的统计报表 |
| 邮件 | smtplib (SMTP_SSL) | 发送通知邮件 |
| HTTP | curl、json | Gitee API 接口调用 |
三、整体架构
入口:entrance.py解析命令行参数(-r release_version),根据参数路由到不同处理函数。-r anolis进入 Anolis 同步流程,-r monitor_euler/merge_euler为预留扩展接口。
核心引擎:errata_automation.py(628 行)是整个工具的调度中枢,按顺序执行:创建工作目录 → 拉取更新列表 → 遍历每个包 → 获取下游源码 → 比较版本 → 分类决策 → 生成 Excel → 发送邮件。
四、核心流程详解
4.1 获取上游更新列表
start()函数通过 Gitee API v5 分页获取src-anolis-os组织下所有仓库(共 39 页,每页 100 个),提取full_name和updated_at字段,与上一次记录的oldlog做 diff,得到本次更新的包列表。
deftest():os.system('rm -rf log')pkg_update=open('log',mode='a',encoding='utf-8')withopen('anolis')asf:data=json.load(f)foriinrange(len(data)):print(data[i]["full_name"]+":"+data[i]["updated_at"],file=pkg_update)pkg_update.close()os.system("sed -i 's/src-anolis-os\\///g' log")curl-XGET--header"Content-Type: application/json;charset=UTF-8"\"https://gitee.com/api/v5/orgs/src-anolis-os/repos?access_token=<TOKEN>&type=all&page=${x}&per_page=100"4.2 分支映射
Anolis OS 有多个分支(a7、a8、a8.2、a8.4、stream-*),每个分支对应下游的不同发行版代号。通过git lg --all查看仓库的提交分支信息,解析出分支名并映射:
defget_update_branchname(pkgname):os.chdir(errata+'/anolis-gitee/'+pkgname)os.system('git pull --all --force')x=os.popen('git lg --all | sed -n 1p')s=x.readline().rstrip("\n")tmp=re.compile(r'[(](.*?)[)]',re.S)xz=re.findall(tmp,s)branch=str(xz)# 分支映射逻辑if'a7,'inbranch:anolis_branch='a7'uos_branch='1000c'uos_tag='c7'elif'a8.2,'inbranchor'a8.4,'inbranchor'a8,'inbranch:anolis_branch='a8.x'uos_branch='105x'uos_tag='c8'elif'stream'inbranch:anolis_branch=uos_branch=stream# 同名映射return[anolis_branch,uos_branch,uos_tag]4.3 获取下游源码
get_uos_src.sh优先从Gerrit服务器克隆下游源码仓库。如果 Gerrit 上没有该包或分支不存在,则回退到Koji查询最新构建,找到对应的.src.rpm并用rpm2cpio + cpio解压:
# 尝试从 Gerrit 克隆gitclone"ssh://<user>@<gerrit-server>:29418/uos/${update_pkgname}"# 失败则回退到 KojipkgNVR=$(koji list-tagged $tag ${update_pkgname}--latest|sed-n'3p'|awk'{print($1)}')kojiPkgDir=$(find/mnt/koji/packages/${update_pkgname}|grep"${pkgNVR}*.src.rpm$")rpm2cpio$kojiPkgDir|cpio-di获取来源(gerrit或koji)会写入getUosSrc文件,供下游的git.sh决策同步方式。
4.4 跳过 Rebrand 提交
Anolis OS 的某些提交只是"改个名"(rebrand),不涉及实质功能变更。git.sh中的anolis_skip_rebrand()会检查最近 5 个提交,遇到 rebrand 提交就自动跳过:
functionanolis_skip_rebrand(){foriin{1..5};docd$anolis_pkgdircommit=$(gitlog-1)result1=$(echo"$commit"|grep"rebrand")result2=$(echo"$commit"|grep"Rebrand")if[[$result1==""]]&&[[$result2==""]];thenbreakelsegitreset--hardHEAD~1fidone}4.5 版本提取
从.spec文件的%changelog中提取版本号。由于各包 Changelog 格式不统一,脚本用了 11 种 awk 模式来覆盖不同格式——格式包括> - <version>、> <version>、- <version>等,邮件域名覆盖.com、.org、.net、.cz、.cn、.name、.fi、.de:
anolis_tmp1=$(cat*.spec|grep-A1"%changelog"|grep".com"|awk-F'> - ''{print $2}')anolis_tmp2=$(cat*.spec|grep-A1"%changelog"|grep".org"|awk-F'> - ''{print $2}')# ...if[["${anolis_tmp1}"!=""]];thenvanolis=${anolis_tmp1}elif[["${anolis_tmp2}"!=""]];thenvanolis=${anolis_tmp2}# ... 依次回退提取出上游版本vanolis和下游版本vuos后,使用sort -V(version sort)比较版本高低。
4.6 分类决策
git.sh将每个包分为三类,核心决策树如下:
关键判断函数skipUosRebuild()解析下游.spec文件的%changelog,统计包含特定标识的条目中有多少是rebuild:
defskipUosRebuild(pkgname):withopen(uos_dir+'%s.spec'%pkgname)asf:a=f.read()b=a.split('%changelog')[1]# 解析 changelog,按 * 分割条目uos=0uos_rebuild=0forxinl:if("rebuild"inx.lower()):# 判断是否为本地下游修改uos=uos+1if"rebuild"inx.lower():uos_rebuild=uos_rebuild+1ifuos==uos_rebuild==0:rebuild="no"# 无下游本地修改elifuos==uos_rebuild!=0:rebuild="all"# 全部是 rebuildelse:rebuild="some"# 有功能性修改returnrebuild4.7 Excel 报告
生成的first.xls包含 5 个 Sheet:
| Sheet 名称 | 内容 | 用途 |
|---|---|---|
| Summary | 分类统计汇总 | 快速了解本次更新概况 |
| Manual | 需人工干预的包 + changelog | 开发人员逐包审核 |
| Auto | 已自动同步的包 + changelog | 追溯确认 |
| Rebuild | 需要重建的包 + changelog | 运维触发 |
| Errors | 版本提取失败的包 | 修复异常 |
Excel 的生成使用xlwt创建工作簿,xlutils.copy追加 Sheet,支持自定义样式(字体、对齐、边框)。
4.8 邮件通知
最后通过 SMTP_SSL 将 Excel 报告发送给团队成员:
mail_host="smtp.exmail.qq.com"mail_user="user@example.com"message=MIMEMultipart()xls=MIMEApplication(open(pwdd+'/first.xls','rb').read())xls.add_header('Content-Disposition','attachment',filename='errata.xlsx')message.attach(xls)message['Subject']=Header('errata自动化更新','utf-8')smtpObj=smtplib.SMTP_SSL(mail_host,'465')smtpObj.login(mail_user,mail_pass)smtpObj.sendmail(sender,receivers,message.as_string())五、项目文件结构
errata/ ├── entrance.py # CLI 入口,参数分发 ├── errata_automation.py # 核心引擎(628 行) ├── utils.py # 工具函数(argparse、pandas) ├── git.sh # 版本比较与同步逻辑(355 行) ├── get_uos_src.sh # 获取下游源码(Gerrit/Koji) ├── download_anolis_src # 下载上游源码包 ├── README # 使用说明 └── errata.png # 流程图六、使用方式
# 安装依赖pip3installxlwt xlrd xlutils pandas numpy# 执行 Anolis 同步python3 entrance.py-ranolis-b<branch>脚本会自动完成:
- 创建工作目录结构
- 从 Gitee API 获取所有仓库的最新更新时间
- 对比上次记录,找出更新的包
- 对每个更新包:克隆源码 → 获取下游源码 → 比较版本 → 分类处理
- 生成 Excel 报告 → 发送邮件
七、总结
本文实现了一套完整的跨发行版包自动同步方案,核心价值在于:
- 自动分类——通过解析 Changelog 智能识别下游定制修改,避免盲目覆盖
- 双重源获取——Gerrit 优先、Koji 回退,适配不同基础设施
- 可视化报告——Excel 多 Sheet 分类 + 邮件通知,团队一目了然
- 可扩展架构——预留了 EulerOS 的接口,可快速适配其他上游
这套方案的思路不仅适用于 Anolis OS 到下游的同步,任何上游-下游架构的发行版维护场景都可以参考复用。
声明:本文为 AI 辅助创作。文中代码示例已做脱敏处理,实际部署时请替换为真实的 token、服务器地址和邮箱配置。