fix(chv-guest): 客机镜像构建缺口补齐(B-02/B-03/B-04/B-05/B-06/B-07/B-08/B-09/B-10/B-13/B-15)
- B-10(实测):virt-customize chroot 里 sshd -t 必败,双因: 无 host key(exit 1)+ 缺 /run/sshd(exit 255);修为 mkdir -p /run/sshd + ssh-keygen -A 临时生成,断言通过后 立删临时 key,镜像仍不带 host key(已在 WSL 真机预演验证) - B-08:Playwright 装在第二次定制,须重写 pip freeze 否则 BUILDINFO 里无 playwright;Chromium 以 ms-playwright 缓存 目录名记录进 guest-versions-chromium.txt - B-09:codex --version 须含 CODEX_VERSION;REPO_COMMIT 须 等于 HEAD 短 hash;工作树脏即拒绝构建 - B-02/B-13:BUILDINFO 生成抽成 buildinfo.py(可单元测试), 补 disk.raw/vmlinux sha256+大小、ubuntu 镜像 sha、cmdline、 playwright 开关、image_sha256 字段 - generate_lockfile.py 改写:版本唯一来源改为 BUILDINFO.versions(客机读回),codex 三件套取 codex_version、/app/host.py 取 repo_commit、chromium 取 versions.chromium;不再查构建宿主 dpkg/pip(旧方法不成立) - check_lockfile.py 补强:条目格式、contents 全覆盖、冻结 清单双向比对 - B-03/B-04 输入格式校验、B-05 curl 重试、B-06 哈希不符 删文件重下、B-07 磁盘预检、B-15 set -euo pipefail - 同步改 test_chv_to10.py:generate 不再查宿主,改断言客机 版本来源 + 不回落宿主
This commit is contained in:
1 parent
8c8d62f70d
commit
7f18c2d13b
5 files changed
+372
-96
No files matched your search
@@ -1,67 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""在构建宿主上生成 CHV 客机镜像的供应链 lockfile(to10 轨)。
|
||||
"""在构建宿主上生成 CHV 客机镜像的供应链 lockfile。
|
||||
|
||||
事后校验见 check_lockfile.py(校验 BUILDINFO.versions);
|
||||
本脚本是事前/事中生成端:在构建宿主上跑,读 BUILDINFO
|
||||
的 tag 与 contents(仅名字清单,不含版本号),逐项从本
|
||||
机 dpkg / pip 查真实版本,写出 lockfile.json:
|
||||
本脚本是生成端:读 BUILDINFO.json,以其中的 versions
|
||||
(build.sh 已从客机读回的 dpkg-query / pip freeze,
|
||||
Playwright=1 时另有 chromium 标识)为唯一版本来源,
|
||||
逐项给 contents 清单配版本,写出 lockfile.json:
|
||||
|
||||
{"tag": ..., "image_sha256": ... | null,
|
||||
"generated_at": UTC ISO 时间,
|
||||
"versions": {"dpkg": ["name version", ...],
|
||||
"pip": ["name version", ...]},
|
||||
"pip": ["name version", ...],
|
||||
"declared": ["name version", ...]},
|
||||
"unresolved": ["查不到版本的名", ...]}
|
||||
|
||||
绝不编造版本号:查不到的进 unresolved,不猜、不填占位
|
||||
版本。--verify 模式校验一份 lockfile:tag 与 BUILDINFO
|
||||
一致、dpkg/pip 非空、每条都有非空版本、unresolved 为空;
|
||||
任一不满足即退出非 0(unresolved 非空 = 清单不完整,
|
||||
不是“生成成功”)。
|
||||
版本来源映射(绝不编造版本号,查不到的进 unresolved):
|
||||
- dpkg 包名 → versions.dpkg("name version" 行)
|
||||
- pip 包名 → versions.pip("name==version" 行)
|
||||
- codex / codex-code-mode-host / codex-resources/bwrap
|
||||
→ BUILDINFO.codex_version(随 codex 一起发布)
|
||||
- /app/host.py → BUILDINFO.repo_commit
|
||||
- chromium → versions.chromium(ms-playwright 缓存目录名)
|
||||
|
||||
注意:旧版逐项查构建宿主的 dpkg/pip,方法不成立——宿主
|
||||
版本与客机版本没有必然关系,且 /app/host.py、codex 三件
|
||||
套在宿主上永远查不到,--verify 永远过不了。本版只读
|
||||
BUILDINFO.versions,不再现场查宿主。
|
||||
|
||||
用法(均在构建宿主上跑):
|
||||
python3 generate_lockfile.py BUILDINFO.json --output lockfile.json
|
||||
python3 generate_lockfile.py --verify lockfile.json \
|
||||
--buildinfo BUILDINFO.json
|
||||
本轨无构建宿主:脚本已落地且可跑(见回执里的 /tmp 合成
|
||||
BUILDINFO 实测),真实清单待构建宿主产出,绝不把合成
|
||||
数据写成生产清单。
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
def _dpkg_version(name):
|
||||
"""dpkg 查版本,查不到回 None(不抛)。"""
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["dpkg-query", "-W", "-f=${Version}", name],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
except Exception:
|
||||
return None
|
||||
if out.returncode != 0:
|
||||
return None
|
||||
version = out.stdout.strip()
|
||||
return version or None
|
||||
# 非包名的内容项:版本来自 BUILDINFO 的声明字段,不是 dpkg/pip。
|
||||
DECLARED_SOURCES = {
|
||||
"codex": "codex_version",
|
||||
"codex-code-mode-host": "codex_version",
|
||||
"codex-resources/bwrap": "codex_version",
|
||||
"/app/host.py": "repo_commit",
|
||||
}
|
||||
|
||||
|
||||
def _pip_version(name):
|
||||
"""pip 查版本,查不到回 None(不抛)。"""
|
||||
try:
|
||||
out = subprocess.run(
|
||||
[sys.executable, "-m", "pip", "show", name],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
except Exception:
|
||||
return None
|
||||
if out.returncode != 0:
|
||||
return None
|
||||
for line in out.stdout.splitlines():
|
||||
if line.startswith("Version:"):
|
||||
version = line.split(":", 1)[1].strip()
|
||||
return version or None
|
||||
return None
|
||||
def _index_dpkg(lines):
|
||||
"""dpkg 行 "name version" → {小写名: "name version"}。"""
|
||||
index = {}
|
||||
for line in lines or []:
|
||||
parts = str(line).split(None, 1)
|
||||
if len(parts) == 2:
|
||||
index[parts[0].lower()] = "%s %s" % (parts[0], parts[1])
|
||||
return index
|
||||
|
||||
|
||||
def _index_pip(lines):
|
||||
"""pip freeze 行 "name==version" → {小写名: "name version"}。"""
|
||||
index = {}
|
||||
for line in lines or []:
|
||||
if "==" in str(line):
|
||||
name, version = str(line).split("==", 1)
|
||||
index[name.lower()] = "%s %s" % (name, version.strip())
|
||||
return index
|
||||
|
||||
|
||||
def _index_chromium(lines):
|
||||
"""chromium 标识行(如 chromium-1200)→ {"chromium": 行}。"""
|
||||
index = {}
|
||||
for line in lines or []:
|
||||
text = str(line).strip()
|
||||
if text:
|
||||
index["chromium"] = "chromium %s" % text
|
||||
return index
|
||||
|
||||
|
||||
def generate(buildinfo_path, output_path):
|
||||
@@ -71,15 +83,32 @@ def generate(buildinfo_path, output_path):
|
||||
if not isinstance(names, list):
|
||||
print("BUILDINFO.contents 不是列表", file=sys.stderr)
|
||||
return 2
|
||||
dpkg, pip, unresolved = [], [], []
|
||||
versions = buildinfo.get("versions") or {}
|
||||
dpkg_index = _index_dpkg(versions.get("dpkg"))
|
||||
pip_index = _index_pip(versions.get("pip"))
|
||||
chromium_index = _index_chromium(versions.get("chromium"))
|
||||
dpkg, pip, declared, unresolved = [], [], [], []
|
||||
for name in names:
|
||||
version = _dpkg_version(name)
|
||||
if version is not None:
|
||||
dpkg.append("%s %s" % (name, version))
|
||||
key = name.lower()
|
||||
if name in DECLARED_SOURCES:
|
||||
version = buildinfo.get(DECLARED_SOURCES[name])
|
||||
if version:
|
||||
declared.append("%s %s" % (name, version))
|
||||
else:
|
||||
unresolved.append(name)
|
||||
continue
|
||||
version = _pip_version(name)
|
||||
if version is not None:
|
||||
pip.append("%s %s" % (name, version))
|
||||
if key == "chromium":
|
||||
entry = chromium_index.get("chromium")
|
||||
if entry:
|
||||
declared.append(entry)
|
||||
else:
|
||||
unresolved.append(name)
|
||||
continue
|
||||
if key in dpkg_index:
|
||||
dpkg.append(dpkg_index[key])
|
||||
continue
|
||||
if key in pip_index:
|
||||
pip.append(pip_index[key])
|
||||
continue
|
||||
unresolved.append(name)
|
||||
lockfile = {
|
||||
@@ -87,7 +116,8 @@ def generate(buildinfo_path, output_path):
|
||||
"image_sha256": buildinfo.get("image_sha256")
|
||||
or buildinfo.get("sha256"),
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"versions": {"dpkg": sorted(dpkg), "pip": sorted(pip)},
|
||||
"versions": {"dpkg": sorted(dpkg), "pip": sorted(pip),
|
||||
"declared": sorted(declared)},
|
||||
"unresolved": sorted(unresolved),
|
||||
}
|
||||
with open(output_path, "w") as fh:
|
||||
@@ -129,9 +159,10 @@ def verify(lockfile_path, buildinfo_path):
|
||||
print("清单不完整,unresolved 非空:%s" % sorted(unresolved),
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
print("lockfile 校验通过:tag=%s dpkg=%d pip=%d"
|
||||
print("lockfile 校验通过:tag=%s dpkg=%d pip=%d declared=%d"
|
||||
% (lockfile.get("tag"), len(versions["dpkg"]),
|
||||
len(versions["pip"])))
|
||||
len(versions["pip"]),
|
||||
len(versions.get("declared") or [])))
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user