#!/usr/bin/python2.7 # Copyright (C) 2015 Brian Murray # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . '''Remove an SRU fom the -proposed pocket for a release. Remove a package from the -proposed pocket for a release of Ubuntu and comment on bug reports regarding the removal of the package giving an explanation that it was removed to due a failure for the SRU bug(s) to be verified in a timely fashion. USAGE: sru-remove -s trusty -p homerun 12345 ''' from __future__ import print_function import optparse import re import subprocess import sys from launchpadlib.launchpad import Launchpad def parse_options(): '''Parse command line arguments. Return (options, [bugs]) tuple. ''' parser = optparse.OptionParser( usage='Usage: %prog [options]') parser.add_option( "-l", "--launchpad", dest="launchpad_instance", default="production") parser.add_option( "-s", dest="release", default=default_release, metavar="RELEASE", help="release (default: %s)" % default_release) parser.add_option( "-p", "--package", dest="sourcepkg") parser.add_option( "--reason", dest="reason", default="ancient", help="reason for removal: ancient, failed (default: ancient)") opts, args = parser.parse_args() if opts.reason not in ('ancient', 'failed'): parser.error('Reason can only be "ancient" or "failed".') return (opts, args) def process_bug(launchpad, distroseries, sourcepkg, num, reason): bug_target_re = re.compile( r'/ubuntu/(?:(?P[^/]+)/)?\+source/(?P[^/]+)$') bug = launchpad.bugs[num] series_name = distroseries.name open_task = False for task in bug.bug_tasks: # Ugly; we have to do URL-parsing to figure this out. # /ubuntu/+source/foo can be fed to launchpad.load() to get a # distribution_source_package, but /ubuntu/hardy/+source/foo can't. match = bug_target_re.search(task.target.self_link) if (not match or (sourcepkg and match.group('source') != sourcepkg)): print("Ignoring task %s in bug %s" % (task.web_link, num)) continue if (match.group('suite') != series_name and match.group('suite') in supported_series and task.status == "Fix Committed"): open_task = True if (match.group('suite') == series_name and task.status == "Fix Committed"): task.status = "Won't Fix" task.lp_save() print("Success: task %s in bug %s" % (task.web_link, num)) btags = bug.tags series_v_needed = 'verification-needed-%s' % series_name if series_v_needed in btags: # this dance is needed due to # https://bugs.launchpad.net/launchpadlib/+bug/254901 tags = btags tags.remove(series_v_needed) bug.tags = tags bug.lp_save() if reason == 'failed': text = ('The version of %s in the proposed pocket of %s that was ' 'purported to fix this bug report has been removed ' 'because one or more bugs that were to be fixed by the ' 'upload have failed verification and been in this state ' 'for more than 10 days.' % (sourcepkg, series_name.title())) else: # 'ancient' text = ('The version of %s in the proposed pocket of %s that was ' 'purported to fix this bug report has been removed ' 'because the bugs that were to be fixed by the upload ' 'were not verified in a timely (105 days) fashion.' % (sourcepkg, series_name.title())) bug.newMessage(content=text, subject='Proposed package removed from archive') # remove verification-needed tag if there are no open tasks if open_task: return # only unsubscribe the teams if there are no open tasks left bug.unsubscribe(person=launchpad.people['ubuntu-sru']) bug.unsubscribe(person=launchpad.people['sru-verification']) if 'verification-needed' in btags: # this dance is needed due to # https://bugs.launchpad.net/launchpadlib/+bug/254901 tags = btags tags.remove('verification-needed') bug.tags = tags bug.lp_save() if __name__ == '__main__': default_release = 'focal' (opts, bugs) = parse_options() if opts.reason == 'failed': removal_comment = ('The package was removed due to one or more of ' 'its SRU bugs having failed verification.') else: # 'ancient' removal_comment = ('The package was removed due to its SRU bug(s) ' 'not being verified in a timely fashion.') launchpad = Launchpad.login_with('sru-remove', opts.launchpad_instance, version="devel") ubuntu = launchpad.distributions['ubuntu'] # determine series for which we issue SRUs supported_series = [] for serie in ubuntu.series: if serie.supported: supported_series.append(serie.name) series = ubuntu.getSeries(name_or_version=opts.release) archive = ubuntu.main_archive existing = [ pkg for pkg in archive.getPublishedSources( exact_match=True, distro_series=series, pocket='Proposed', source_name=opts.sourcepkg, status='Published')] if not existing: print("ERROR: %s was not found in -proposed for release %s." % (opts.sourcepkg, opts.release), file=sys.stderr) sys.exit(1) rm_p_cmd = ["remove-package", "-m", removal_comment, "-y", "-l", opts.launchpad_instance, "-s", "%s-proposed" % opts.release, opts.sourcepkg] ret = subprocess.call(rm_p_cmd) if ret != 0: print("ERROR: There was an error removing %s from %s-proposed.\n" "The remove-package command returned %s." % (opts.sourcepkg, opts.release, ret), file=sys.stderr) sys.exit(1) # only comment on the bugs after removing the package for bug in bugs: process_bug(launchpad, series, opts.sourcepkg, bug, opts.reason)