Files
codex-app-server/chv-guest/generate_lockfile.py
T

161 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""在构建宿主上生成 CHV 客机镜像的供应链 lockfile(to10 轨)。
事后校验见 check_lockfile.py(校验 BUILDINFO.versions);
本脚本是事前/事中生成端:在构建宿主上跑,读 BUILDINFO
的 tag 与 contents(仅名字清单,不含版本号),逐项从本
机 dpkg / pip 查真实版本,写出 lockfile.json:
{"tag": ..., "image_sha256": ... | null,
"generated_at": UTC ISO 时间,
"versions": {"dpkg": ["name version", ...],
"pip": ["name version", ...]},
"unresolved": ["查不到版本的名", ...]}
绝不编造版本号:查不到的进 unresolved,不猜、不填占位
版本。--verify 模式校验一份 lockfile:tag 与 BUILDINFO
一致、dpkg/pip 非空、每条都有非空版本、unresolved 为空;
任一不满足即退出非 0(unresolved 非空 = 清单不完整,
不是“生成成功”)。
用法(均在构建宿主上跑):
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
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 generate(buildinfo_path, output_path):
with open(buildinfo_path) as fh:
buildinfo = json.load(fh)
names = buildinfo.get("contents") or []
if not isinstance(names, list):
print("BUILDINFO.contents 不是列表", file=sys.stderr)
return 2
dpkg, pip, unresolved = [], [], []
for name in names:
version = _dpkg_version(name)
if version is not None:
dpkg.append("%s %s" % (name, version))
continue
version = _pip_version(name)
if version is not None:
pip.append("%s %s" % (name, version))
continue
unresolved.append(name)
lockfile = {
"tag": buildinfo.get("tag"),
"image_sha256": buildinfo.get("image_sha256")
or buildinfo.get("sha256"),
"generated_at": datetime.now(timezone.utc).isoformat(),
"versions": {"dpkg": sorted(dpkg), "pip": sorted(pip)},
"unresolved": sorted(unresolved),
}
with open(output_path, "w") as fh:
json.dump(lockfile, fh, indent=2, sort_keys=True)
fh.write("\n")
if unresolved:
print("警告:%d 项查不到版本,已列入 unresolved:%s"
% (len(unresolved), ", ".join(sorted(unresolved))),
file=sys.stderr)
return 0
def verify(lockfile_path, buildinfo_path):
with open(lockfile_path) as fh:
lockfile = json.load(fh)
if buildinfo_path:
with open(buildinfo_path) as fh:
buildinfo = json.load(fh)
if lockfile.get("tag") != buildinfo.get("tag"):
print("tag 不一致:lockfile %s != BUILDINFO %s"
% (lockfile.get("tag"), buildinfo.get("tag")),
file=sys.stderr)
return 1
versions = lockfile.get("versions") or {}
for kind in ("dpkg", "pip"):
entries = versions.get(kind)
if not entries:
print("versions.%s 为空,供应链清单缺失" % kind,
file=sys.stderr)
return 1
for entry in entries:
parts = str(entry).split()
if len(parts) < 2 or not parts[1]:
print("版本为空的条目:%s %r" % (kind, entry),
file=sys.stderr)
return 1
unresolved = lockfile.get("unresolved") or []
if unresolved:
print("清单不完整,unresolved 非空:%s" % sorted(unresolved),
file=sys.stderr)
return 1
print("lockfile 校验通过:tag=%s dpkg=%d pip=%d"
% (lockfile.get("tag"), len(versions["dpkg"]),
len(versions["pip"])))
return 0
def main(argv):
if "--verify" in argv:
lockfile_path = argv[argv.index("--verify") + 1]
buildinfo_path = None
if "--buildinfo" in argv:
buildinfo_path = argv[argv.index("--buildinfo") + 1]
return verify(lockfile_path, buildinfo_path)
if len(argv) < 2:
print("用法:generate_lockfile.py BUILDINFO.json "
"--output lockfile.json | --verify lockfile.json "
"[--buildinfo BUILDINFO.json]", file=sys.stderr)
return 2
output_path = None
if "--output" in argv:
output_path = argv[argv.index("--output") + 1]
if not output_path:
print("缺少 --output", file=sys.stderr)
return 2
return generate(argv[1], output_path)
if __name__ == "__main__":
raise SystemExit(main(sys.argv))