[Merge] ~vorlon/ubuntu-dev-tools:pm-helper into ubuntu-dev-tools:main
Simon Quigley
mp+444677 at code.launchpad.net
Mon Jan 29 04:37:48 UTC 2024
Review: Approve
I'm no longer going to block on test cases. Let's move this forward, if there is interest in a test case we can address that later.
I apologize for not looking at this sooner, given the last uploads to ubuntu-dev-tools are from me. I will take care of the upload tomorrow, along with some testing and any necessary fixes.
I noticed two potential pedantic items, I'll check them further then fix as appropriate.
Thank you all for your hard work on this! I'm certain this will improve collaboration.
Diff comments:
> diff --git a/pm-helper b/pm-helper
> new file mode 100755
> index 0000000..9cdc176
> --- /dev/null
> +++ b/pm-helper
> @@ -0,0 +1,149 @@
> +#!/usr/bin/python3
> +# Find the next thing to work on for proposed-migration
> +# Copyright (C) 2023 Canonical Ltd.
> +# Author: Steve Langasek <steve.langasek at ubuntu.com>
> +
> +# This program is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU General Public License, version 3.
> +
> +# 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 <http://www.gnu.org/licenses/>.
> +
> +import lzma
> +from argparse import ArgumentParser
> +import sys
> +import webbrowser
> +import yaml
> +
> +from launchpadlib.launchpad import Launchpad
> +
> +from ubuntutools.utils import get_url
> +
> +
> +# proposed-migration is only concerned with the devel series; unlike other
> +# tools, don't make this configurable
> +excuses_url = 'https://ubuntu-archive-team.ubuntu.com/proposed-migration/' \
> + + 'update_excuses.yaml.xz'
> +
> +
> +def get_proposed_version(excuses, package):
> + for k in excuses['sources']:
> + if k['source'] == package:
> + return k.get('new-version')
> + return None
> +
> +
> +def claim_excuses_bug(launchpad, bug, package):
> + print("LP: #%d: %s" % (bug.id, bug.title))
> + ubuntu = launchpad.distributions['ubuntu']
> + series = ubuntu.current_series.fullseriesname
> +
> + for task in bug.bug_tasks:
> + # targeting to a series doesn't make the default task disappear,
> + # it just makes it useless
> + if task.bug_target_name == "%s (%s)" % (package, series):
> + our_task = task
> + break
> + elif task.bug_target_name == "%s (Ubuntu)" % package:
> + our_task = task
> +
> + if our_task.assignee == launchpad.me:
> + print("Bug already assigned to you.")
> + return True
> + elif our_task.assignee:
> + print("Currently assigned to %s" % our_task.assignee.name)
> +
> + print('''Do you want to claim this bug? [yN] ''', end="")
> + sys.stdout.flush()
> + response = sys.stdin.readline()
> + if response.strip().lower().startswith('y'):
> + our_task.assignee = launchpad.me
> + our_task.lp_save()
> + return True
> +
> + return False
> +
> +
> +def create_excuses_bug(launchpad, package, version):
> + print("Will open a new bug")
> + bug = launchpad.bugs.createBug(
> + title = 'proposed-migration for %s %s' % (package, version),
> + tags = ('update-excuse'),
> + target = 'https://api.launchpad.net/devel/ubuntu/+source/%s' % package,
This needs testing with the staging Launchpad instance, since production is default but other options are supported.
> + description = '%s %s is stuck in -proposed.' % (package, version)
> + )
> +
> + task = bug.bug_tasks[0]
> + task.assignee = launchpad.me
> + task.lp_save()
> +
> + print("Opening %s in browser" % bug.web_link)
> + webbrowser.open(bug.web_link)
> + return bug
> +
> +
> +def has_excuses_bugs(launchpad, package):
> + ubuntu = launchpad.distributions['ubuntu']
> + pkg = ubuntu.getSourcePackage(name=package)
> + if not pkg:
> + raise ValueError(f"No such source package: {package}")
> +
> + tasks = pkg.searchTasks(tags=['update-excuse'], order_by=['id'])
> +
> + bugs = [task.bug for task in tasks]
> + if not bugs:
> + return False
> +
> + if len(bugs) == 1:
> + print("There is 1 open update-excuse bug against %s" % package)
> + else:
> + print("There are %d open update-excuse bugs against %s" \
> + % (len(bugs), package))
> +
> + for bug in bugs:
> + if claim_excuses_bug(launchpad, bug, package):
> + return True
> +
> + return True
> +
> +
> +def main():
> + parser = ArgumentParser()
> + parser.add_argument(
> + "-l", "--launchpad", dest="launchpad_instance", default="production")
> + parser.add_argument(
> + "-v", "--verbose", default=False, action="store_true",
> + help="be more verbose")
> + parser.add_argument(
> + 'package', nargs='?', help="act on this package only")
> + args = parser.parse_args()
> +
> + args.launchpad = Launchpad.login_with(
> + "pm-helper", args.launchpad_instance, version="devel")
> +
> + f = get_url(excuses_url, False)
> + with lzma.open(f) as lzma_f:
> + excuses = yaml.load(lzma_f, Loader=yaml.CSafeLoader)
> +
> + if args.package:
> + try:
> + if not has_excuses_bugs(args.launchpad, args.package):
> + proposed_version = get_proposed_version(excuses, args.package)
> + if not proposed_version:
> + print("Package %s not found in -proposed." % args.package)
> + sys.exit(1)
> + create_excuses_bug(args.launchpad, args.package,
> + proposed_version)
> + except ValueError as e:
> + sys.stderr.write(f"{e}\n")
> + else:
> + pass # for now
> +
> +
> +if __name__ == '__main__':
> + sys.exit(main())
> diff --git a/ubuntutools/utils.py b/ubuntutools/utils.py
> new file mode 100644
> index 0000000..dcc354e
> --- /dev/null
> +++ b/ubuntutools/utils.py
> @@ -0,0 +1,82 @@
> +# Copyright (C) 2019-2023 Canonical Ltd.
> +# Author: Brian Murray <brian.murray at canonical.com> et al.
> +
> +# 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 <http://www.gnu.org/licenses/>.
> +
> +"""Portions of archive related code that is re-used by various tools."""
> +
> +from datetime import datetime
> +import os
> +import re
> +import urllib.request
> +
> +import dateutil.parser
> +from dateutil.tz import tzutc
> +
> +
> +def get_cache_dir():
> + cache_dir = os.environ.get('XDG_CACHE_HOME',
> + os.path.expanduser(os.path.join('~', '.cache')))
> + uat_cache = os.path.join(cache_dir, 'ubuntu-archive-tools')
Probably needs to be ubuntu-dev-tools, no?
> + os.makedirs(uat_cache, exist_ok=True)
> + return uat_cache
> +
> +
> +def get_url(url, force_cached):
> + ''' Return file to the URL, possibly caching it
> + '''
> + cache_file = None
> +
> + # ignore bileto urls wrt caching, they're usually too small to matter
> + # and we don't do proper cache expiry
> + m = re.search('ubuntu-archive-team.ubuntu.com/proposed-migration/'
> + '([^/]*)/([^/]*)',
> + url)
> + if m:
> + cache_dir = get_cache_dir()
> + cache_file = os.path.join(cache_dir, '%s_%s' % (m.group(1), m.group(2)))
> + else:
> + # test logs can be cached, too
> + m = re.search(
> + 'https://autopkgtest.ubuntu.com/results/autopkgtest-[^/]*/([^/]*)/([^/]*)'
> + '/[a-z0-9]*/([^/]*)/([_a-f0-9]*)@/log.gz',
> + url)
> + if m:
> + cache_dir = get_cache_dir()
> + cache_file = os.path.join(
> + cache_dir, '%s_%s_%s_%s.gz' % (
> + m.group(1), m.group(2), m.group(3), m.group(4)))
> +
> + if cache_file:
> + try:
> + prev_mtime = os.stat(cache_file).st_mtime
> + except FileNotFoundError:
> + prev_mtime = 0
> + prev_timestamp = datetime.fromtimestamp(prev_mtime, tz=tzutc())
> + new_timestamp = datetime.now(tz=tzutc()).timestamp()
> + if force_cached:
> + return open(cache_file, 'rb')
> +
> + f = urllib.request.urlopen(url)
> +
> + if cache_file:
> + remote_ts = dateutil.parser.parse(f.headers['last-modified'])
> + if remote_ts > prev_timestamp:
> + with open('%s.new' % cache_file, 'wb') as new_cache:
> + for line in f:
> + new_cache.write(line)
> + os.rename('%s.new' % cache_file, cache_file)
> + os.utime(cache_file, times=(new_timestamp, new_timestamp))
> + f.close()
> + f = open(cache_file, 'rb')
> + return f
--
https://code.launchpad.net/~vorlon/ubuntu-dev-tools/+git/ubuntu-dev-tools/+merge/444677
Your team Ubuntu Development Team is subscribed to branch ubuntu-dev-tools:main.
More information about the Ubuntu-reviews
mailing list