Merge ~apw/ubuntu-archive-tools:new-review--add-build-analysis2 into ubuntu-archive-tools:main

Proposed by Andy Whitcroft
Status: Merged
Merged at revision: 8a48f981b6477e6ec0a844303632b0488ae7a09b
Proposed branch: ~apw/ubuntu-archive-tools:new-review--add-build-analysis2
Merge into: ubuntu-archive-tools:main
Diff against target: 484 lines (+232/-114)
2 files modified
new-review (+3/-1)
new_review.py (+229/-113)
Reviewer Review Type Date Requested Status
Ubuntu Package Archive Administrators Pending
Review via email: mp+455300@code.launchpad.net

Commit message

new-review: add support for dumping build related information

Use the build logs to identify the applied archive pockets at build time. Also use the logs to reconstruct the binary control files for the new and old packages and produce a delta.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/new-review b/new-review
2index 5a7ddd5..521df2f 100755
3--- a/new-review
4+++ b/new-review
5@@ -66,7 +66,8 @@ class NewReview:
6 signing_request = ReviewRequest.select(args, services=self.svcs)
7 if signing_request.task_assign(args):
8 signing_request.analyse(args)
9- signing_request.binary_new(args)
10+ if not args.approve:
11+ signing_request.binary_new(args)
12
13 signing_bot = SigningBotSubmit()
14 signing_bot.submit(args, signing_request)
15@@ -102,6 +103,7 @@ class NewReview:
16 group.add_argument("--from-suite", dest="from_suite")
17 group.add_argument("--to", dest="to_route")
18 group.add_argument("--to-suite", dest="to_suite")
19+ group.add_argument("--existing-suite", dest="existing_suite")
20 group.add_argument("--embargo", action="store_true")
21 group = subcmd.add_argument_group(title='type/package options')
22 group.add_argument("--force-type")
23diff --git a/new_review.py b/new_review.py
24index b47bedf..23c82ad 100755
25--- a/new_review.py
26+++ b/new_review.py
27@@ -8,6 +8,7 @@ import hashlib
28 import json
29 import os
30 import re
31+import shlex
32 import sys
33 from argparse import ArgumentParser
34 from copy import copy
35@@ -81,6 +82,7 @@ class Package:
36 self.signing = signing
37
38 self.series, self.pocket = Package.suite2series_pocket(self.suite)
39+ self.order = order
40 self.package_key = (order, name)
41
42 @classmethod
43@@ -274,9 +276,10 @@ class ReviewRequest:
44 def analyse(self, args):
45 logger.info("Promotion Review:")
46
47- def signing_reference_suite(self, reference, suite):
48+ def signing_reference_suite(self, reference, suite, via="unknown"):
49 self.signing_ref = reference
50 self.signing_suite = suite
51+ self.signing_via = via
52
53 def proposed_reference_suite(self, reference, suite):
54 self.proposed_ref = reference
55@@ -406,6 +409,63 @@ class ReviewRequest:
56
57 return binaries
58
59+ def build_analyse(self, build):
60+ lp = self.svcs.launchpad
61+
62+ abi_re = re.compile(r"^([0-9]+\.[0-9]+\.[0-9]+)[\.-]([0-9]+)")
63+
64+ version = build.source_package_version
65+ match = abi_re.search(version)
66+ if match:
67+ abi = match.group(1) + "-" + match.group(2)
68+ else:
69+ abi = None
70+
71+ log_url = build.build_log_url
72+ if log_url is None:
73+ logger.warn("{} build log not available".format(build.arch_tag))
74+ return None, None
75+ log_url = log_url.replace('https://launchpad.net/', 'https://api.launchpad.net/devel/')
76+ log = lp._browser.get(log_url)
77+ control = []
78+ in_package_contents = 0
79+ in_package_control = False
80+ for line in log.decode('utf-8', 'backslashreplace').rstrip().split('\n'):
81+ if line.startswith("RUN: /usr/share/launchpad-buildd/bin/in-target override-sources-list"):
82+ pockets = set()
83+ bits = shlex.split(line)
84+ for bit in bits:
85+ if bit.startswith("deb http://ftpmaster.internal/ubuntu "):
86+ suite = bit.split()[2]
87+ if "-" in suite:
88+ pocket = suite.split("-")[-1]
89+ else:
90+ pocket = "release"
91+ pockets.add(pocket)
92+
93+ elif line.startswith("| Package contents"):
94+ in_package_contents = 2
95+
96+ elif line.startswith("+-----------------"):
97+ in_package_contents -= 1
98+
99+ if in_package_contents > 0:
100+ if line.startswith(" Package:"):
101+ in_package_control = True
102+ elif line.startswith(" Installed-Size:"):
103+ continue
104+ elif len(line) == 0:
105+ if in_package_control:
106+ control.append("")
107+ in_package_control = False
108+ if in_package_control:
109+ line = line.replace(version, "VERSION")
110+ if abi is not None:
111+ line = line.replace(abi, "ABI")
112+ control.append(line)
113+
114+ return pockets, control
115+
116 def binary_new(self, args, against="updates"):
117 logger.info("New Review:")
118 existing_group = PackageGroup("control")
119@@ -413,6 +473,8 @@ class ReviewRequest:
120 if pocket_ref is None:
121 continue
122 some = False
123+ if args.existing_suite is not None:
124+ pocket_suite = args.existing_suite
125 for package in self.package_group.packages:
126 if package.generate:
127 continue
128@@ -433,6 +495,7 @@ class ReviewRequest:
129 src,
130 signing=package.signing,
131 main=package.main,
132+ order=package.order,
133 )
134 )
135 if some:
136@@ -443,6 +506,7 @@ class ReviewRequest:
137 else:
138 logger.info(" No binaries found in updates/release, New source packages?")
139
140+ logger.info(" published-binaries:")
141 old_bins = self.binary_list(existing_group, args, ignore_failures=True)
142 new_bins = self.binary_list(self.package_group, args)
143 if old_bins is not None and new_bins is not None:
144@@ -452,17 +516,60 @@ class ReviewRequest:
145 if line.rstrip() in ("+++", "---"):
146 continue
147 some = True
148- logger.info(" " + line)
149+ logger.info(" " + line)
150 if not some:
151- logger.info(" No Changes ({} binaries)".format(len(new_bins)))
152+ logger.info(" No Changes ({} binaries)".format(len(new_bins)))
153
154 elif self.have_autonew:
155- logger.info(" binary builds not complete but automated new-review available")
156+ logger.info(" binary builds not complete but automated new-review available")
157 self.need_autonew = True
158
159 else:
160 raise ReviewError("Binary Builds incomplete, cannot review")
161
162+ logger.info(" built-against:")
163+ against = None
164+ against_detail = []
165+ against_dump = False
166+ new_control = []
167+ for package in self.package_group.packages:
168+ for build in package.spph.getBuilds():
169+ pockets, control = self.build_analyse(build)
170+ if pockets is not None and not package.generate:
171+ if against is None:
172+ against = pockets
173+ if against != pockets:
174+ against_dump = True
175+ against_detail.append(" {}/{} ubuntu/primary {}".format(package.name, build.arch_tag, ",".join(sorted(pockets))))
176+ new_control.append("=== {}/{} ===".format(package.name, build.arch_tag))
177+ new_control += control
178+
179+ if against_dump is True:
180+ for against in against_detail:
181+ logger.info(against)
182+ else:
183+ logger.info(" all ubuntu/primary {}".format(",".join(sorted(against))))
184+
185+ if old_bins is not None and new_bins is not None:
186+ logger.info(" control-delta:")
187+ old_control = []
188+ for package in existing_group.packages:
189+ for build in package.spph.getBuilds():
190+ pockets, control = self.build_analyse(build)
191+ if pockets is not None:
192+ old_control.append("=== {}/{} ===".format(package.name, build.arch_tag))
193+ old_control += control
194+
195+ delta = unified_diff(old_control, new_control, lineterm="")
196+ some = False
197+ for line in delta:
198+ if line.rstrip() in ("+++", "---"):
199+ continue
200+ some = True
201+ logger.info(" " + line)
202+ if not some:
203+ logger.info(" No Changes")
204+
205 def lp_find_tracker_task(self, tracker_id, name):
206 try:
207 lp_tracker = self.svcs.launchpad.bugs[tracker_id]
208@@ -586,113 +693,136 @@ class ReviewRequestKernel(ReviewRequest):
209 if package.main:
210 ucs_srcs.append(package.spph)
211
212- if (
213- self.signing_present
214- and self.signing_ref is not None
215- and self.signing_ref.startswith("signing:")
216- ):
217+ if self.signing_present:
218 logger.info("Signing Review:")
219- logger.info(" key {} selected".format(self.signing_ref[8:]))
220-
221- # VALIDATE signing routing.
222- compatibility = []
223- if len(ucs_srcs) == 0:
224- # Go find main...
225- main_version_re = re.compile("(.*)\+[0-9]")
226- main_name = self.ks_source.lookup_package(type="main").name
227- for package in self.package_group.packages:
228- if not package.signing:
229- continue
230- match = main_version_re.match(package.version)
231- if match:
232- main_version = match.group(1)
233- else:
234- main_version = package_version
235-
236- for ucs_ref, ucs_pocket in self.proposed_routes:
237- lp_archive = lp.archives.getByReference(reference=ucs_ref)
238- if lp_archive is None:
239- raise ReviewError("{}: archive '{}' not found".format(tracker_id, ucs_ref))
240- srcs = lp_archive.getPublishedSources(exact_match=True, order_by_date=True, distro_series='/ubuntu/' + self.series, source_name=main_name, version=main_version)
241- if len(srcs) == 0:
242+ if (
243+ self.signing_ref is not None
244+ and self.signing_ref.startswith("signing:")
245+ ):
246+ signing_config = self.svcs.signing_config
247+ signing_descriptor = signing_config.lookup_stream(self.signing_ref[8:])
248+ signing_reference = signing_descriptor.archive_reference
249+
250+ # VALIDATE signing routing.
251+ compatibility = []
252+ if len(ucs_srcs) == 0:
253+ # Go find main...
254+ main_version_re = re.compile("(.*)\+[0-9]")
255+ main_name = self.ks_source.lookup_package(type="main").name
256+ for package in self.package_group.packages:
257+ if not package.signing:
258 continue
259- src = srcs[0]
260- logger.info(" using {}:{} {} (main package) from {} for Ubuntu-Signing-Compatible".format(self.series, main_name, main_version, ucs_ref))
261- ucs_srcs.append(src)
262- break
263- for package_spph in ucs_srcs:
264- changes_url = package_spph.changesFileUrl()
265- if changes_url is None:
266- raise ReviewError("{}: no changes file for {} not found".format(args.tracker, ks_package.type or "main"))
267-
268- # If we managed to find a changes file then we can extract the list.
269- changes_url = changes_url.replace('https://launchpad.net/', 'https://api.launchpad.net/devel/')
270- changes = lp._browser.get(changes_url)
271- compatibility = None
272- for line in changes.decode('utf-8').rstrip().split('\n'):
273- if line.startswith("Ubuntu-Compatible-Signing:"):
274- compatibility = line.split()[1:]
275- logger.info(" Ubuntu-Compatible-Signing {} source".format(signing_compat(compatibility)))
276-
277- # If there are builds available check they are matching compatibility.
278- compatibility_match = True
279- for build in package_spph.getBuilds():
280- # If we managed to find a changes file then we can extract the list.
281- changes_url = build.changesfile_url
282+ match = main_version_re.match(package.version)
283+ if match:
284+ main_version = match.group(1)
285+ else:
286+ main_version = package_version
287+
288+ for ucs_ref, ucs_pocket in self.proposed_routes:
289+ lp_archive = lp.archives.getByReference(reference=ucs_ref)
290+ if lp_archive is None:
291+ raise ReviewError("{}: archive '{}' not found".format(tracker_id, ucs_ref))
292+ srcs = lp_archive.getPublishedSources(exact_match=True, order_by_date=True, distro_series='/ubuntu/' + self.series, source_name=main_name, version=main_version)
293+ if len(srcs) == 0:
294+ continue
295+ src = srcs[0]
296+ logger.info(" using {}:{} {} (main package) from {} for Ubuntu-Signing-Compatible".format(self.series, main_name, main_version, ucs_ref))
297+ ucs_srcs.append(src)
298+ break
299+ for package_spph in ucs_srcs:
300+ changes_url = package_spph.changesFileUrl()
301 if changes_url is None:
302- logger.warn("Ubuntu-Compatible-Signing - {} build not available".format(build.arch_tag))
303- continue
304- changes_url = build.changesfile_url.replace('https://launchpad.net/', 'https://api.launchpad.net/devel/')
305+ raise ReviewError("{}: no changes file for {} not found".format(args.tracker, ks_package.type or "main"))
306+
307+ # If we managed to find a changes file then we can extract the list.
308+ changes_url = changes_url.replace('https://launchpad.net/', 'https://api.launchpad.net/devel/')
309 changes = lp._browser.get(changes_url)
310- build_compat = None
311+ compatibility = None
312 for line in changes.decode('utf-8').rstrip().split('\n'):
313 if line.startswith("Ubuntu-Compatible-Signing:"):
314- build_compat = line.split()[1:]
315- if compatibility != build_compat:
316- compatibility_match = False
317- logger.info(" Ubuntu-Compatible-Signing {} {}".format(signing_compat(build_compat), build.arch_tag))
318-
319- # Report compatibility error.
320- if not compatibility_match:
321- raise ReviewError("compatibility missmatch".format(ks_package.type or "main"))
322-
323- signing_config = self.svcs.signing_config
324- signing_descriptor = signing_config.lookup_stream(self.signing_ref[8:])
325-
326- if compatibility is None:
327- compatibility = ["ubuntu/2", "pro/2", "kariya/2"]
328- logger.info(" Ubuntu-Compatible-Signing implied as {}".format(signing_compat(compatibility)))
329-
330- compatible = None
331- for stream_level in compatibility:
332- compatible_descriptor = signing_config.lookup_stream(stream_level)
333- if signing_descriptor.stream != compatible_descriptor.stream:
334- continue
335- compatible = signing_descriptor.level == compatible_descriptor.level
336- break
337+ compatibility = line.split()[1:]
338+ ucs_detail = [(compatibility, "source")]
339+
340+ # If there are builds available check they are matching compatibility.
341+ compatibility_match = True
342+ for build in package_spph.getBuilds():
343+ # If we managed to find a changes file then we can extract the list.
344+ changes_url = build.changesfile_url
345+ if changes_url is None:
346+ logger.warn("Ubuntu-Compatible-Signing - {} build not available".format(build.arch_tag))
347+ continue
348+ changes_url = build.changesfile_url.replace('https://launchpad.net/', 'https://api.launchpad.net/devel/')
349+ changes = lp._browser.get(changes_url)
350+ build_compat = None
351+ for line in changes.decode('utf-8').rstrip().split('\n'):
352+ if line.startswith("Ubuntu-Compatible-Signing:"):
353+ build_compat = line.split()[1:]
354+ if compatibility != build_compat:
355+ compatibility_match = False
356+ ucs_detail.append((build_compat, build.arch_tag))
357+
358+ # Report compatibility error.
359+ logger.info(" Ubuntu-Compatible-Signing:")
360+ if not compatibility_match:
361+ for detail_compatibility, detail_thing in ucs_detail:
362+ logger.info(" {} {}".format(detail_thing, signing_compat(detail_compatibility)))
363+ raise ReviewError("compatibility missmatch".format(ks_package.type or "main"))
364+ logger.info(" all {}".format(signing_compat(compatibility)))
365+
366+ if compatibility is None:
367+ compatibility = ["ubuntu/2", "pro/2", "kariya/2"]
368+ logger.info(" Ubuntu-Compatible-Signing implied as {}".format(signing_compat(compatibility)))
369+
370+ compatible = None
371+ for stream_level in compatibility:
372+ compatible_descriptor = signing_config.lookup_stream(stream_level)
373+ if signing_descriptor.stream != compatible_descriptor.stream:
374+ continue
375+ compatible = signing_descriptor.level == compatible_descriptor.level
376+ break
377+
378+ # XXX: this really permits us to use the wrong keys, it would be better
379+ # if everything used like ibm-gt/1 but ...
380+ compatibility.append("*/1")
381+ #print("APW", signing_descriptor.level, type(signing_descriptor.level))
382+ if compatible is None or signing_descriptor.level == 1:
383+ compatible = 1
384+
385+ #print(compatibility, signing_descriptor.stream, signing_descriptor.level, compatible)
386+ if not compatible:
387+ raise ReviewError("{}: signing-level {}/{} not compatible with package {}".format(args.tracker, signing_descriptor.stream, signing_descriptor.level, " ".join(compatibility)))
388+ logger.info(" key {}:".format(self.signing_ref[8:]))
389+ logger.info(" selected via {}".format(self.signing_via))
390+ logger.info(" compatible with package {}".format(" ".join(compatibility)))
391
392- # XXX: this really permits us to use the wrong keys, it would be better
393- # if everything used like ibm-gt/1 but ...
394- compatibility.append("*/1")
395- #print("APW", signing_descriptor.level, type(signing_descriptor.level))
396- if compatible is None or signing_descriptor.level == 1:
397- compatible = 1
398+ else:
399+ signing_reference = self.signing_ref
400
401- #print(compatibility, signing_descriptor.stream, signing_descriptor.level, compatible)
402- if not compatible:
403- raise ReviewError("{}: signing-level {}/{} not compatible with package {}".format(args.tracker, signing_descriptor.stream, signing_descriptor.level, " ".join(compatibility)))
404- logger.info(" key {}/{} deemed compatible with package {}".format(signing_descriptor.stream, signing_descriptor.level, " ".join(compatibility)))
405+ logger.info(" signing-ppa {}".format(signing_reference))
406
407- elif self.signing_present:
408- logger.info("Signing Review:")
409- logger.info(" using signing PPA {}".format(self.signing_ref))
410+ some = False
411+ ptypes = set()
412+ for package in self.package_group.packages:
413+ ptypes.add(package.type or "main")
414+ for package in self.package_group.packages:
415+ ptype = package.type or "main"
416+ signing_from = self.ks_source.lookup_package(type=ptype).signing_from
417+ if signing_from is None:
418+ continue
419+ if not some:
420+ some = True
421+ logger.info(" pairings:")
422+ sf_ptype = signing_from.type or "main"
423+ logger.info(" {} with {}".format(ptype, sf_ptype))
424+ if sf_ptype not in ptypes:
425+ raise ReviewError("{}: {} in request without {}, request inconsistent ...".format(tracker_id, ptype, sf_ptype))
426
427 def set_routing(self, args, ks_routing, from_route_name, from_route_entry):
428 if self.signing_present:
429 signing_routes = ks_routing.lookup_route('signing')
430 if len(signing_routes) > 0:
431 signing_route = signing_routes[0]
432- self.signing_reference_suite(signing_route.raw_reference, signing_route.suite)
433+ self.signing_reference_suite(signing_route.raw_reference, signing_route.suite, via="kernel-series")
434
435 if args.to_route is not None:
436 to_route_name, to_route_entry = self.route_split(args.to_route_name)
437@@ -811,22 +941,6 @@ class ReviewRequestKernelTracker(ReviewRequestKernel):
438 if tb_promote is None:
439 raise ReviewError("{}: {} promotion data not present".format(tracker_id, task))
440
441- # Sanity check ...
442- # XXX: this should be done in validate based on the package names and types, so that
443- # it applied equally to handles and could then be extended to Packages.
444- some = False
445- for ptype in tb_promote:
446- signing_from = ks_source.lookup_package(type=ptype).signing_from
447- if signing_from is None:
448- continue
449- if not some:
450- some = True
451- logger.info(" pairings:")
452- sf_ptype = signing_from.type or "main"
453- logger.info(" {} with {}".format(ptype, sf_ptype))
454- if sf_ptype not in tb_promote:
455- raise ReviewError("{}: {} in request without {}, request inconsistent ...".format(tracker_id, ptype, sf_ptype))
456-
457 packages = PackageGroup()
458 for package_type in tb_promote:
459 package_name = tb_packages.get(package_type)
460@@ -859,6 +973,7 @@ class ReviewRequestKernelTracker(ReviewRequestKernel):
461 generate=ks_package.signing_to is not None,
462 signing=ks_package.signing_from is not None,
463 main=package_type == "main",
464+ type=package_type,
465 order=self.ks_package_order(ks_package),
466 )
467 )
468@@ -953,6 +1068,7 @@ class ReviewRequestKernelHandle(ReviewRequestKernel):
469 generate=ks_package.signing_to is not None,
470 signing=ks_package.signing_from is not None,
471 main=package_type == "main",
472+ type=package_type,
473 order=self.ks_package_order(ks_package),
474 )
475 )
476@@ -1115,7 +1231,7 @@ class ReviewRequestPackages(ReviewRequest):
477 logger.info(" key is not driver selected key {}".format(default_key))
478
479 signing_ref = "signing:" + key
480- self.signing_reference_suite(signing_ref, Package.series_pocket2suite(series_name, "Release"))
481+ self.signing_reference_suite(signing_ref, Package.series_pocket2suite(series_name, "Release"), via=key_source)
482
483 proposed_ref, proposed_suite = args.to_route, args.to_suite or series_name
484 if proposed_ref is None:

Subscribers

People subscribed via source and target branches