-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnotarize.py
More file actions
109 lines (96 loc) · 4.86 KB
/
Copy pathnotarize.py
File metadata and controls
109 lines (96 loc) · 4.86 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
#!/usr/bin/python3
"""Submit one retained ZIP, then ask notarytool to wait once for up to five minutes.
Only a recognized Accepted response permits later Makefile steps. This wrapper
never retries, polls itself, staples, rebuilds, or infers status from prose.
"""
import argparse
import json
from pathlib import Path
import shutil
import sys
import uuid
import zipfile
sys.dont_write_bytecode = True
sys.path.insert(0, str(Path(__file__).resolve().parent / 'tests/lib'))
from artifact import digest
from release_commands import command, save
import release_evidence
def response(path):
def unique_fields(pairs):
value = {}
for key, item in pairs:
if key in value:
raise ValueError('ambiguous duplicate response field')
value[key] = item
return value
try:
value = json.loads(path.read_bytes(), object_pairs_hook=unique_fields)
except (ValueError, OSError) as exc:
raise RuntimeError(f'unrecognized Apple response; inspect {path} and its sibling stderr') from exc
if not isinstance(value, dict):
raise RuntimeError(f'unrecognized Apple response; inspect {path}')
return value
def submission_id(value):
raw = value.get('id')
if not isinstance(raw, str):
raise RuntimeError('Apple response has no recognized submission ID; inspect raw output before any new submission')
try:
return str(uuid.UUID(raw))
except ValueError as exc:
raise RuntimeError('unrecognized submission ID; inspect raw output before any new submission') from exc
def submit(archive, profile, out, *, invoke=command):
report = dict(schema_version=1, archive=str(archive), state='stopped', submission_id=None)
save(out / 'result.json', report)
try:
report['sha256'] = digest(archive)
retained = out / 'submitted.zip'
shutil.copyfile(archive, retained)
if digest(retained) != report['sha256']:
raise RuntimeError('archive changed while retaining the submission')
save(out / 'result.json', report)
invoke(out / 'submit', ['/usr/bin/xcrun', 'notarytool', 'submit', retained,
'--keychain-profile', profile, '--no-wait', '--output-format', 'json'], timeout=180)
report['submission_id'] = submission_id(response(out / 'submit/stdout'))
save(out / 'result.json', report)
# A separate wait retains the ID even when Apple never finishes. The
# outer deadline also covers hangs outside notarytool's own wait timer.
invoke(out / 'wait', ['/usr/bin/xcrun', 'notarytool', 'wait', report['submission_id'],
'--keychain-profile', profile, '--timeout', '5m', '--output-format', 'json'], timeout=330)
answer = response(out / 'wait/stdout')
report['observed_status'] = answer.get('status')
if submission_id(answer) != report['submission_id'] or answer.get('status') != 'Accepted':
raise RuntimeError('no recognized Accepted response for this submission; inspect wait output; no continuation performed')
if digest(archive) != report['sha256'] or digest(retained) != report['sha256']:
raise RuntimeError('archive changed during submission; do not continue with the current dist artifacts')
report['state'] = 'accepted'
except (OSError, ValueError, RuntimeError, KeyboardInterrupt) as exc:
report['error'] = str(exc) or 'interrupted'
print(f'STOP: {report["error"]}\nRetained submission: {out}\nSee docs/SIGNING.md before continuing.', file=sys.stderr)
finally:
save(out / 'result.json', report)
return 0 if report['state'] == 'accepted' else 1
def main():
parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
parser.add_argument('archive', type=Path)
parser.add_argument('keychain_profile')
parser.add_argument('--evidence-dir', type=Path, help='existing release attempt from release_evidence.py')
args = parser.parse_args()
archive = args.archive.resolve()
if not archive.is_file() or not args.keychain_profile.strip():
parser.error('provide an existing ZIP and an explicit keychain profile')
try:
session = args.evidence_dir.resolve() if args.evidence_dir else release_evidence.create(archive)
record = release_evidence.check_archive(session, archive)
if digest(archive) != record['submitted_sha256']:
raise ValueError('archive changed since release evidence was created')
out = session / 'notarization'
out.mkdir() # Never overwrite an earlier submission or retry it implicitly.
except (OSError, ValueError, KeyError, zipfile.BadZipFile) as exc:
parser.error(str(exc))
print(f'Notarization evidence: {out}', flush=True)
try:
return submit(archive, args.keychain_profile, out)
finally:
release_evidence.summarize(session)
if __name__ == '__main__':
raise SystemExit(main())