forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprepare-desktop-beta-promotion.py
More file actions
executable file
·156 lines (139 loc) · 5.68 KB
/
Copy pathprepare-desktop-beta-promotion.py
File metadata and controls
executable file
·156 lines (139 loc) · 5.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/env python3
"""Validate a qualified desktop candidate and build its control-plane manifest."""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from desktop_release_metadata import ( # noqa: E402
fail,
parse_metadata,
)
from desktop_qualification_evidence import verify_evidence # noqa: E402
from desktop_release_manifest import validate_manifest # noqa: E402
TAG_RE = re.compile(r"^v(?P<version>\d+\.\d+(?:\.\d+)?)\+(?P<build>\d+)-macos$")
def _asset(release: dict, names: set[str]) -> dict:
for asset in release.get("assets", []):
if asset.get("name") in names:
return asset
fail(f"release is missing required asset: {', '.join(sorted(names))}")
def prepare_manifest(
release: dict,
release_tag: str,
target_sha: str,
zip_sha256: str,
dmg_sha256: str,
*,
qualification_evidence: dict,
qualification_evidence_sha256: str,
allow_stable_channel: bool = False,
) -> dict:
if release.get("tagName") != release_tag:
fail(f"release tag mismatch: expected {release_tag}, got {release.get('tagName')}")
match = TAG_RE.match(release_tag)
if not match:
fail(f"invalid macOS release tag: {release_tag}")
if release.get("isDraft") or release.get("isPrerelease"):
fail("release must be published and not a GitHub prerelease")
metadata = parse_metadata(release.get("body") or "")
channel = metadata.get("channel")
is_live = metadata.get("isLive", "").lower()
if channel == "candidate" and is_live not in {"false", "0", "no"}:
fail("candidate release must have isLive: false")
if channel == "beta" and is_live not in {"true", "1", "yes"}:
fail("beta release must have isLive: true")
if channel == "stable" and is_live not in {"true", "1", "yes"}:
fail("stable release must have isLive: true")
allowed_channels = {"candidate", "beta"}
if allow_stable_channel:
allowed_channels.add("stable")
if channel not in allowed_channels:
accepted = "candidate, beta, or stable" if allow_stable_channel else "candidate or beta"
fail(f"release channel must be {accepted}, got {channel!r}")
zip_asset = _asset(release, {"Omi.zip"})
dmg_asset = _asset(release, {"omi.dmg"})
signature = metadata.get("edSignature", "").strip()
if not signature:
fail("release is missing Sparkle signature")
try:
verify_evidence(
qualification_evidence,
release,
release_tag,
target_sha,
{
"Omi.zip": zip_sha256,
dmg_asset["name"]: dmg_sha256,
},
)
except ValueError as exc:
raise ValueError(str(exc)) from exc
changelog = [item.strip() for item in metadata.get("changelog", "").split("|") if item.strip()]
version = match.group("version")
build = int(match.group("build"))
published_at = release.get("publishedAt")
if not isinstance(published_at, str):
fail("release is missing publishedAt")
manifest = {
"schema_version": 1,
"release_id": release_tag,
"platform": "macos",
"version": version,
"build_number": build,
"app_source_sha": target_sha,
"zip_url": zip_asset.get("url"),
"dmg_url": dmg_asset.get("url"),
"zip_sha256": f"sha256:{zip_sha256}",
"dmg_sha256": f"sha256:{dmg_sha256}",
"ed_signature": signature,
"qualification_evidence_asset": metadata.get("qualifiedBetaEvidence") or f"qualification-evidence-{release_tag}.json",
"qualification_evidence_sha256": qualification_evidence_sha256,
"qualification_tier": "T2",
"qualification_passed": True,
"backend_mode": "app_only",
"compatibility_contract": {
"schema_version": 1,
"app_release_id": release_tag,
"app_version": version,
"app_build_number": build,
"backend_mode": "app_only",
"environment_contract_version": "desktop-backend-env-v1",
},
"environment_contract_version": "desktop-backend-env-v1",
"created_at": published_at,
"published_at": published_at,
"changelog": changelog,
"mandatory": metadata.get("mandatory", "false").lower() in {"true", "1", "yes"},
}
return validate_manifest(manifest)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--release-json", required=True)
parser.add_argument("--release-tag", required=True)
parser.add_argument("--target-sha", required=True)
parser.add_argument("--zip-sha256", required=True)
parser.add_argument("--dmg-sha256", required=True)
parser.add_argument("--qualification-evidence", required=True)
parser.add_argument("--qualification-evidence-sha256", required=True)
parser.add_argument("--allow-stable-channel", action="store_true")
parser.add_argument("--output", required=True)
args = parser.parse_args()
release = json.loads(Path(args.release_json).read_text())
evidence = json.loads(Path(args.qualification_evidence).read_text())
manifest = prepare_manifest(
release,
args.release_tag,
args.target_sha,
args.zip_sha256,
args.dmg_sha256,
qualification_evidence=evidence,
qualification_evidence_sha256=args.qualification_evidence_sha256,
allow_stable_channel=args.allow_stable_channel,
)
Path(args.output).write_text(json.dumps(manifest, indent=2) + "\n")
print(f"qualified beta manifest prepared: {args.release_tag}")
return 0
if __name__ == "__main__":
raise SystemExit(main())