Update ubuntu-archive-tools.

master
Simon Quigley 4 years ago
parent 64dd1fc405
commit 363622addf

@ -1 +1 @@
1209 cjwatson@canonical.com-20190220074107-dvkdscxl2y2ww9j6 1324 steve.langasek@canonical.com-20200516195933-ymljxy32gq2m13p9

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python2.7
# Check for override mismatches between architectures # Check for override mismatches between architectures
# Copyright (C) 2005, 2008, 2009, 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2005, 2008, 2009, 2010, 2011, 2012 Canonical Ltd.

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python2.7
# Copyright 2009-2012 Canonical Ltd. This software is licensed under the # Copyright 2009-2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3. # GNU Affero General Public License version 3.

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python2.7
# Copyright (C) 2016 Canonical Ltd. # Copyright (C) 2016 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python3
# Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2012 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>
@ -19,67 +19,74 @@
from __future__ import print_function from __future__ import print_function
from optparse import OptionParser import argparse
from launchpadlib.launchpad import Launchpad from launchpadlib.launchpad import Launchpad
import lazr.restfulclient.errors
def branch_livefses(options, owner): def branch_livefses(args, owner):
for livefs in list(options.launchpad.livefses): for livefs in list(args.launchpad.livefses):
if (livefs.owner == owner and if (livefs.owner == owner and
livefs.distro_series == options.source_series): livefs.distro_series == args.source_series):
print("Branching %s for %s ..." % ( print("Branching %s for %s ..." % (
livefs.web_link, options.dest_series.name)) livefs.web_link, args.dest_series.name))
new_livefs = options.launchpad.livefses.new( try:
owner=owner, distro_series=options.dest_series, new_livefs = args.launchpad.livefses.getByName(
name=livefs.name, metadata=livefs.metadata) owner=owner, distro_series=args.dest_series,
name=livefs.name)
except lazr.restfulclient.errors.NotFound:
new_livefs = args.launchpad.livefses.new(
owner=owner, distro_series=args.dest_series,
name=livefs.name, metadata=livefs.metadata)
new_livefs.require_virtualized = livefs.require_virtualized new_livefs.require_virtualized = livefs.require_virtualized
new_livefs.relative_build_score = livefs.relative_build_score new_livefs.relative_build_score = livefs.relative_build_score
new_livefs.lp_save() try:
new_livefs.lp_save()
except lazr.restfulclient.errors.Unauthorized:
print("Could not devirt ppa, ask Launchpad team for support.")
pass
print(" %s" % new_livefs.web_link) print(" %s" % new_livefs.web_link)
def main(): def main():
parser = OptionParser(usage="usage: %prog [options] OWNER") parser = argparse.ArgumentParser()
parser.add_option( parser.add_argument(
"-l", "--launchpad", dest="launchpad_instance", default="production") "-l", "--launchpad", dest="launchpad_instance", default="production")
parser.add_option( parser.add_argument(
"-d", "--distribution", default="ubuntu", metavar="DISTRIBUTION", "-d", "--distribution", default="ubuntu", metavar="DISTRIBUTION",
help="branch live filesystems for DISTRIBUTION") help="branch live filesystems for DISTRIBUTION")
parser.add_option( parser.add_argument(
"--source-series", "--source-series",
help="source series (default: current stable release)") help="source series (default: current stable release)")
parser.add_option( parser.add_argument(
"--dest-series", "--dest-series",
help="destination series (default: series in pre-release freeze)") help="destination series (default: series in pre-release freeze)")
options, args = parser.parse_args() parser.add_argument("owner", help="owner of live filesystems to copy")
if not args: args = parser.parse_args()
parser.error(
"You must specify an owner whose live filesystems you want to " args.launchpad = Launchpad.login_with(
"copy.") "branch-livefses", args.launchpad_instance, version="devel")
options.launchpad = Launchpad.login_with( distro = args.launchpad.distributions[args.distribution]
"branch-livefses", options.launchpad_instance, version="devel") if args.source_series is None:
args.source_series = [
distro = options.launchpad.distributions[options.distribution]
if options.source_series is None:
options.source_series = [
series for series in distro.series series for series in distro.series
if series.status == "Current Stable Release"][0] if series.status == "Current Stable Release"][0]
else: else:
options.source_series = distro.getSeries( args.source_series = distro.getSeries(
name_or_version=options.source_series) name_or_version=args.source_series)
if options.dest_series is None: if args.dest_series is None:
options.dest_series = [ args.dest_series = [
series for series in distro.series series for series in distro.series
if series.status == "Pre-release Freeze"][0] if series.status == "Pre-release Freeze"][0]
else: else:
options.dest_series = distro.getSeries( args.dest_series = distro.getSeries(
name_or_version=options.dest_series) name_or_version=args.dest_series)
owner = options.launchpad.people[args[0]] owner = args.launchpad.people[args.owner]
branch_livefses(options, owner) branch_livefses(args, owner)
if __name__ == '__main__': if __name__ == '__main__':

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python2.7
# Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2012 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>
@ -22,6 +22,7 @@ from __future__ import print_function
from optparse import OptionParser from optparse import OptionParser
import os import os
import re import re
import time
import subprocess import subprocess
try: try:
from urllib.parse import urlparse from urllib.parse import urlparse
@ -134,8 +135,18 @@ def branch(options, collection):
if "git.launchpad.net" in remote: if "git.launchpad.net" in remote:
lp_git_repo = options.launchpad.git_repositories.getByPath( lp_git_repo = options.launchpad.git_repositories.getByPath(
path=urlparse(remote).path.lstrip("/")) path=urlparse(remote).path.lstrip("/"))
lp_git_repo.default_branch = options.dest_series new_ref = "refs/heads/%s" % options.dest_series
lp_git_repo.lp_save() # Sometimes it takes LP a while to notice the new ref
for i in range(10):
if lp_git_repo.getRefByPath(path=new_ref):
lp_git_repo.default_branch = new_ref
lp_git_repo.lp_save()
break
time.sleep(1)
else:
raise Exception(
"Was unable to set default_branch of %s after "
"multiple retries - proceed manually." % remote)
else: else:
raise Exception( raise Exception(
"Git remote URL must be on git.launchpad.net.") "Git remote URL must be on git.launchpad.net.")

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python3
# Copyright 2012 Canonical Ltd. # Copyright 2012 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python2.7
# Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd.
@ -17,18 +17,16 @@
from __future__ import print_function from __future__ import print_function
from collections import defaultdict from collections import defaultdict
import gzip
import optparse import optparse
import os import os
import re import re
import sys import sys
import tempfile
import apt_pkg from utils import read_tag_file
default_base = '/home/ubuntu-archive/mirror/ubuntu' default_base = '/home/ubuntu-archive/mirror/ubuntu'
default_suite = 'disco' default_suite = 'groovy'
components = ('main', 'restricted', 'universe', 'multiverse') components = ('main', 'restricted', 'universe', 'multiverse')
# Cut-down RE from deb822.PkgRelation. # Cut-down RE from deb822.PkgRelation.
@ -65,34 +63,6 @@ def ports_arches(suite):
return ('arm64', 'armhf', 'ppc64el', 's390x') return ('arm64', 'armhf', 'ppc64el', 's390x')
def read_tag_file(path):
tmp = tempfile.NamedTemporaryFile(prefix='checkrdepends.', delete=False)
try:
compressed = gzip.open(path)
try:
tmp.write(compressed.read())
finally:
compressed.close()
tmp.close()
with open(tmp.name) as uncompressed:
tag_file = apt_pkg.TagFile(uncompressed)
prev_name = None
prev_stanza = None
for stanza in tag_file:
try:
name = stanza['package']
except KeyError:
continue
if name != prev_name and prev_stanza is not None:
yield prev_stanza
prev_name = name
prev_stanza = stanza
if prev_stanza is not None:
yield prev_stanza
finally:
os.unlink(tmp.name)
def read_sources(path): def read_sources(path):
ret = { ret = {
'binary': {}, 'binary': {},

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# Sync a suite with a Seed list. # Sync a suite with a Seed list.
# Copyright (C) 2004, 2005, 2009, 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2004, 2005, 2009, 2010, 2011, 2012 Canonical Ltd.
@ -22,8 +22,6 @@
# listed for promotion at once # listed for promotion at once
# i.e. to allow 'change-override -S' usage # i.e. to allow 'change-override -S' usage
from __future__ import print_function
__metaclass__ = type __metaclass__ = type
import atexit import atexit
@ -36,6 +34,7 @@ try:
except ImportError: except ImportError:
from cgi import escape from cgi import escape
import json import json
from operator import attrgetter
from optparse import OptionParser from optparse import OptionParser
import os import os
import shutil import shutil
@ -43,10 +42,7 @@ import sys
import tempfile import tempfile
from textwrap import dedent from textwrap import dedent
import time import time
try: from urllib.parse import quote_plus
from urllib.parse import quote_plus
except ImportError:
from urllib import quote_plus
import apt_pkg import apt_pkg
from launchpadlib.launchpad import Launchpad from launchpadlib.launchpad import Launchpad
@ -128,7 +124,7 @@ def read_current_source(options):
archive_source[pkg] = ( archive_source[pkg] = (
version, component.split("/")[0]) version, component.split("/")[0])
for pkg, (version, component) in archive_source.items(): for pkg, (version, component) in list(archive_source.items()):
if component in options.components: if component in options.components:
current_source[pkg] = (version, component) current_source[pkg] = (version, component)
@ -140,9 +136,7 @@ def read_current_binary(options):
components_with_di.append('%s/debian-installer' % component) components_with_di.append('%s/debian-installer' % component)
for suite in options.suites: for suite in options.suites:
for component in components_with_di: for component in components_with_di:
for arch in [ for arch in options.architectures:
"i386", "amd64", "armhf", "arm64", "ppc64el",
"s390x"]:
binaries_path = "%s/dists/%s/%s/binary-%s/Packages.gz" % ( binaries_path = "%s/dists/%s/%s/binary-%s/Packages.gz" % (
options.archive_dir, suite, component, arch) options.archive_dir, suite, component, arch)
for section in apt_pkg.TagFile(decompress_open(binaries_path)): for section in apt_pkg.TagFile(decompress_open(binaries_path)):
@ -161,7 +155,7 @@ def read_current_binary(options):
archive_binary[pkg][0], version) < 0: archive_binary[pkg][0], version) < 0:
archive_binary[pkg] = (version, component, src) archive_binary[pkg] = (version, component, src)
for pkg, (version, component, src) in archive_binary.items(): for pkg, (version, component, src) in list(archive_binary.items()):
if component in options.components: if component in options.components:
current_binary[pkg] = (version, component, src) current_binary[pkg] = (version, component, src)
@ -183,8 +177,7 @@ def read_germinate(options):
# ideally supported+build-depends too, but Launchpad's # ideally supported+build-depends too, but Launchpad's
# cron.germinate doesn't save this # cron.germinate doesn't save this
for arch in ["i386", "amd64", "armhf", "arm64", "ppc64el", for arch in options.architectures:
"s390x"]:
for seed in seeds: for seed in seeds:
filename = "%s/%s_%s_%s_%s" % ( filename = "%s/%s_%s_%s_%s" % (
options.germinate_path, seed, flavour, options.suite, arch) options.germinate_path, seed, flavour, options.suite, arch)
@ -264,13 +257,13 @@ def find_signer(options, source):
exact_match=True) exact_match=True)
if not publications: if not publications:
return('no publications found', '') return('no publications found', '')
sorted_pubs = sorted([(ps.date_published, ps) sorted_pubs = sorted(
for ps in publications [ps for ps in publications if ps.date_published is not None],
if ps.date_published is not None], reverse=True) key=attrgetter('date_published'), reverse=True)
for pub in sorted_pubs: for pub in sorted_pubs:
if pub[1].package_signer: if pub.package_signer:
signer = pub[1].package_signer.name signer = pub.package_signer.name
web_link = pub[1].package_signer.web_link web_link = pub.package_signer.web_link
return(signer, web_link) return(signer, web_link)
else: else:
signer = '' signer = ''
@ -343,8 +336,8 @@ def do_dot(why, fd, mir_bugs, suite):
fd.write( fd.write(
'digraph "component-mismatches: movements to main/restricted" {\n') 'digraph "component-mismatches: movements to main/restricted" {\n')
for s, binwhy in why.iteritems(): for s, binwhy in why.items():
for binary, why in binwhy.iteritems(): for binary, why in binwhy.items():
# ignore binaries from this source, and "rescued" # ignore binaries from this source, and "rescued"
if why in binwhy or why.startswith('Rescued'): if why in binwhy or why.startswith('Rescued'):
continue continue
@ -472,7 +465,7 @@ def get_teams(options, source):
if os.path.exists(options.package_team_mapping): if os.path.exists(options.package_team_mapping):
with open(options.package_team_mapping) as ptm_file: with open(options.package_team_mapping) as ptm_file:
for team, packages in json.load(ptm_file).items(): for team, packages in list(json.load(ptm_file).items()):
if team == "unsubscribed": if team == "unsubscribed":
continue continue
for package in packages: for package in packages:
@ -575,7 +568,7 @@ def do_output(options,
package_team_mapping = defaultdict(set) package_team_mapping = defaultdict(set)
if os.path.exists(options.package_team_mapping): if os.path.exists(options.package_team_mapping):
with open(options.package_team_mapping) as ptm_file: with open(options.package_team_mapping) as ptm_file:
for team, packages in json.load(ptm_file).items(): for team, packages in (json.load(ptm_file).items()):
if team == "unsubscribed": if team == "unsubscribed":
continue continue
for package in packages: for package in packages:
@ -741,7 +734,7 @@ def do_output(options,
} }
results["source demotions"] += len(output) results["source demotions"] += len(output)
for title, output_spec in all_output.items(): for title, output_spec in list(all_output.items()):
source_and_binary = output_spec.get("source_and_binary", False) source_and_binary = output_spec.get("source_and_binary", False)
binary_only = output_spec.get("binary_only", False) binary_only = output_spec.get("binary_only", False)
print_section_text( print_section_text(
@ -882,6 +875,9 @@ def main():
else: else:
options.suites = [options.suite] options.suites = [options.suite]
options.series = options.distro.getSeries(name_or_version=options.suites[0])
options.architectures = [a.architecture_tag for a in options.series.architectures]
if options.output_file is not None: if options.output_file is not None:
sys.stdout = open('%s.new' % options.output_file, 'w') sys.stdout = open('%s.new' % options.output_file, 'w')
if options.html_output_file is not None: if options.html_output_file is not None:
@ -889,6 +885,11 @@ def main():
else: else:
options.html_output = None options.html_output = None
# Force encoding to UTF-8 even in non-UTF-8 locales.
import io
sys.stdout = io.TextIOWrapper(
sys.stdout.detach(), encoding="UTF-8", line_buffering=True)
options.time = time.time() options.time = time.time()
options.timestamp = time.strftime( options.timestamp = time.strftime(
'%a %b %e %H:%M:%S %Z %Y', time.gmtime(options.time)) '%a %b %e %H:%M:%S %Z %Y', time.gmtime(options.time))

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python3
# Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2012 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python3
# Copyright (C) 2011, 2012 Canonical Ltd. # Copyright (C) 2011, 2012 Canonical Ltd.
# Author: Martin Pitt <martin.pitt@canonical.com> # Author: Martin Pitt <martin.pitt@canonical.com>
@ -24,87 +24,429 @@ USAGE:
from __future__ import print_function from __future__ import print_function
import argparse import argparse
from contextlib import contextmanager
from copy import copy
from io import StringIO
import sys import sys
import unittest
from launchpadlib.launchpad import Launchpad from launchpadlib.launchpad import Launchpad
from kernel_series import KernelSeries
class TestBase(unittest.TestCase):
class FakeArgs:
def __init__(self, **kwargs):
self.testing = True
self.series = None
self.source = None
self.ppa2 = False
self.security = False
self.security2 = False
self.esm = False
self.fips = False
self.ibmgt = False
self.to_signing = False
self.from_signing = False
self.no_auto = False
self.update(**kwargs)
def update(self, **kwargs):
for (key, value) in kwargs.items():
setattr(self, key, value)
return self
@contextmanager
def capture(self):
new_out, new_err = StringIO(), StringIO()
old_out, old_err = sys.stdout, sys.stderr
try:
sys.stdout, sys.stderr = new_out, new_err
yield sys.stdout, sys.stderr
finally:
sys.stdout, sys.stderr = old_out, old_err
@classmethod
def setUpClass(cls):
data = """
defaults:
routing-table:
default:
security-build:
- ['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release' ]
- ['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release' ]
build:
- ['ppa:canonical-kernel-team/ubuntu/ppa', 'Release' ]
proposed:
- ['ubuntu', 'Proposed' ]
esm:
security-build:
- ['ppa:canonical-kernel-security-team/ubuntu/esm', 'Release']
build:
- ['ppa:canonical-kernel-esm/ubuntu/ppa', 'Release']
signing:
- ['ppa:canonical-signing/ubuntu/esm', 'Release']
proposed:
- ['ppa:canonical-kernel-esm/ubuntu/proposed', 'Release']
14.04:
codename: trusty
supported: true
esm: true
sources:
linux:
packages:
linux:
linux-signed:
type: signed
linux-meta:
type: meta
16.04:
codename: xenial
supported: true
sources:
linux-fips:
routing:
security-build:
- ['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release']
- ['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release']
build:
- ['ppa:fips-cc-stig/ubuntu/fips-build', 'Release']
signing:
- ['ppa:canonical-signing/ubuntu/fips', 'Release']
proposed:
- ['ppa:ubuntu-advantage/ubuntu/fips-proposed', 'Release']
packages:
linux-fips:
linux-meta-fips:
type: meta
linux-signed-fips:
type: signed
18.04:
codename: bionic
supported: true
sources:
linux:
packages:
linux:
linux-signed:
type: signed
linux-meta:
type: meta
linux-ibm-gt:
routing:
security-build:
- ['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release']
- ['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release']
build:
- ['ppa:ibm-cloud/ubuntu/build', 'Release']
proposed:
- ['ppa:ibm-cloud/ubuntu/proposed', 'Release']
packages:
linux-ibm-gt:
linux-meta-ibm-gt:
type: meta
"""
cls.ks = KernelSeries(data=data)
class TestRouting(TestBase):
def test_default(self):
expected = (['ppa:canonical-kernel-team/ubuntu/ppa', 'Release'], ['ubuntu', 'Proposed'], False)
result = routing(self.FakeArgs(series='bionic', source='linux'), self.ks)
self.assertEqual(expected, result)
def test_security(self):
expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release'], ['ubuntu', 'Proposed'], True)
result = routing(self.FakeArgs(series='bionic', source='linux', security=True), self.ks)
self.assertEqual(expected, result)
def test_security2(self):
expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release'], ['ubuntu', 'Proposed'], True)
result = routing(self.FakeArgs(series='bionic', source='linux', security2=True), self.ks)
self.assertEqual(expected, result)
def test_to_signing(self):
expected = (['ppa:canonical-kernel-team/ubuntu/ppa', 'Release'], None, False)
result = routing(self.FakeArgs(series='bionic', source='linux', to_signing=True), self.ks)
self.assertEqual(expected, result)
def test_from_signing(self):
expected = (None, ['ubuntu', 'Proposed'], False)
result = routing(self.FakeArgs(series='bionic', source='linux', from_signing=True), self.ks)
self.assertEqual(expected, result)
def test_esm(self):
expected = (['ppa:canonical-kernel-esm/ubuntu/ppa', 'Release'], ['ppa:canonical-signing/ubuntu/esm', 'Release'], False)
result = routing(self.FakeArgs(series='trusty', source='linux'), self.ks)
self.assertEqual(expected, result)
def test_esm_security(self):
expected = (['ppa:canonical-kernel-security-team/ubuntu/esm', 'Release'], ['ppa:canonical-signing/ubuntu/esm', 'Release'], False)
result = routing(self.FakeArgs(series='trusty', source='linux', security=True), self.ks)
self.assertEqual(expected, result)
def test_esm_security2(self):
with self.assertRaises(SystemExit), self.capture() as (out, err):
expected = (None, ['ppa:canonical-kernel-esm/ubuntu/proposed', 'Release'], False)
result = routing(self.FakeArgs(series='trusty', source='linux', security2=True), self.ks)
self.assertEqual(expected, result)
def test_esm_to_signing(self):
expected = (['ppa:canonical-kernel-esm/ubuntu/ppa', 'Release'], ['ppa:canonical-signing/ubuntu/esm', 'Release'], False)
result = routing(self.FakeArgs(series='trusty', source='linux', esm=True, to_signing=True), self.ks)
self.assertEqual(expected, result)
def test_esm_from_signing(self):
expected = (['ppa:canonical-signing/ubuntu/esm', 'Release'], ['ppa:canonical-kernel-esm/ubuntu/proposed', 'Release'], False)
result = routing(self.FakeArgs(series='trusty', source='linux', esm=True, from_signing=True), self.ks)
self.assertEqual(expected, result)
# Autorouting will enable to_signing, the user will then want to switch us
# to from_signing in order to perform phase two copies. To ensure this is
# simple we make from_signing take presidence over to_signing. Test this
# is honoured correctly.
def test_esm_from_signing_override_to_signing(self):
expected = (['ppa:canonical-signing/ubuntu/esm', 'Release'], ['ppa:canonical-kernel-esm/ubuntu/proposed', 'Release'], False)
result = routing(self.FakeArgs(series='trusty', source='linux', esm=True, to_signing=True, from_signing=True), self.ks)
self.assertEqual(expected, result)
def test_fips(self):
expected = (['ppa:fips-cc-stig/ubuntu/fips-build', 'Release'], ['ppa:canonical-signing/ubuntu/fips', 'Release'], False)
result = routing(self.FakeArgs(series='xenial', source='linux-fips'), self.ks)
self.assertEqual(expected, result)
def test_fips_security(self):
expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release'], ['ppa:canonical-signing/ubuntu/fips', 'Release'], False)
result = routing(self.FakeArgs(series='xenial', source='linux-fips', security=True), self.ks)
self.assertEqual(expected, result)
def test_fips_security2(self):
expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release'], ['ppa:canonical-signing/ubuntu/fips', 'Release'], False)
result = routing(self.FakeArgs(series='xenial', source='linux-fips', security2=True), self.ks)
self.assertEqual(expected, result)
def test_fips_to_signing(self):
expected = (['ppa:fips-cc-stig/ubuntu/fips-build', 'Release'], ['ppa:canonical-signing/ubuntu/fips', 'Release'], False)
result = routing(self.FakeArgs(series='xenial', source='linux-fips', to_signing=True), self.ks)
self.assertEqual(expected, result)
def test_fips_from_signing(self):
expected = (['ppa:canonical-signing/ubuntu/fips', 'Release'], ['ppa:ubuntu-advantage/ubuntu/fips-proposed', 'Release'], False)
result = routing(self.FakeArgs(series='xenial', source='linux-fips', from_signing=True), self.ks)
self.assertEqual(expected, result)
def test_ibmgt(self):
expected = (['ppa:ibm-cloud/ubuntu/build', 'Release'], ['ppa:ibm-cloud/ubuntu/proposed', 'Release'], False)
result = routing(self.FakeArgs(series='bionic', source='linux-ibm-gt'), self.ks)
self.assertEqual(expected, result)
def test_ibmgt_security(self):
expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release'], ['ppa:ibm-cloud/ubuntu/proposed', 'Release'], False)
result = routing(self.FakeArgs(series='bionic', source='linux-ibm-gt', security=True), self.ks)
self.assertEqual(expected, result)
def test_ibmgt_security2(self):
expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release'], ['ppa:ibm-cloud/ubuntu/proposed', 'Release'], False)
result = routing(self.FakeArgs(series='bionic', source='linux-ibm-gt', security2=True), self.ks)
self.assertEqual(expected, result)
def routing(args, ks):
series_name = args.series
package_name = args.source
series = ks.lookup_series(codename=series_name)
if series is None:
print("ERROR: {} -- series unknown".format(series_name))
sys.exit(1)
package = None
package_signed = None
for source_srch in series.sources:
package_signed = None
for package_srch in source_srch.packages:
if package_srch.name == package_name:
package = package_srch
if (package_srch.name.startswith('linux-signed-') or
package_srch.name == 'linux-signed'):
package_signed = package_srch
if package is not None:
break
if package is None:
print("ERROR: {}/{} -- package unknown".format(series_name, package_name))
sys.exit(1)
source = package.source
routing = source.routing
if routing is None:
print("ERROR: {}/{} -- package has no routing".format(series_name, package_name))
sys.exit(1)
build_archives = routing.lookup_destination('build')
security_archives = routing.lookup_destination('security-build')
proposed_archive = routing.lookup_destination('proposed', primary=True)
signing_archive = routing.lookup_destination('signing', primary=True)
if build_archives is None or len(build_archives) < 1:
print("ERROR: {}/{} -- package has no primary build archive".format(series_name, package_name))
sys.exit(1)
if args.ppa2 and (build_archives is None or len(build_archives) < 2):
print("ERROR: {}/{} -- package has no secondary build archive".format(series_name, package_name))
sys.exit(1)
if build_archives is None:
print("ERROR: {}/{} -- package has no build archive".format(series_name, package_name))
sys.exit(1)
if proposed_archive is None:
print("ERROR: {}/{} -- package has no proposed archive".format(series_name, package_name))
sys.exit(1)
if args.security and (security_archives is None or len(security_archives) < 1):
print("ERROR: {}/{} -- package has no primary security archive".format(series_name, package_name))
sys.exit(1)
if args.security2 and (security_archives is None or len(security_archives) < 2):
print("ERROR: {}/{} -- package has no secondary security archive".format(series_name, package_name))
sys.exit(1)
# Default route build -> proposed
if args.ppa2:
from_archive = build_archives[1]
else:
from_archive = build_archives[0]
to_archive = proposed_archive
unembargo = False
# Handle security routing.
if args.security:
from_archive = security_archives[0]
if args.security2:
from_archive = security_archives[1]
# Allow us to unembargo when releasing from security to ubuntu.
if (args.security or args.security2) and to_archive[0] == 'ubuntu':
unembargo = True
# Handle signing routing.
if args.from_signing:
from_archive = signing_archive
elif args.to_signing:
to_archive = signing_archive
# Automatically route to signing by default.
elif args.no_auto is False and signing_archive is not None and package_signed is not None:
to_archive = signing_archive
# Announce the routing if needed.
if (args.testing is False and (routing.name != 'default' or from_archive == signing_archive or to_archive == signing_archive)):
msg = "NOTE: directing copy using {} routes".format(routing.name)
if from_archive == signing_archive:
msg += ' from signing'
elif to_archive == signing_archive:
msg += ' to signing'
print(msg)
return (from_archive, to_archive, unembargo)
# SELF-TESTS:
if len(sys.argv) >= 2 and sys.argv[1] == '--self-test':
unittest.main(argv=[sys.argv[0]] + sys.argv[2:])
sys.exit(0)
parser = argparse.ArgumentParser(description='Copy a proposed kernel to the apropriate archive pocket') parser = argparse.ArgumentParser(description='Copy a proposed kernel to the apropriate archive pocket')
parser.set_defaults(testing=False)
parser.add_argument('--dry-run', action='store_true', help='Do everything but actually copy the package') parser.add_argument('--dry-run', action='store_true', help='Do everything but actually copy the package')
parser.add_argument('--ppa2', action='store_true', help='Copy from the kernel build PPA2')
parser.add_argument('--security', '-S', action='store_true', help='Copy from the kernel security PPA') parser.add_argument('--security', '-S', action='store_true', help='Copy from the kernel security PPA')
parser.add_argument('--security2', action='store_true', help='Copy from the kernel security PPA2') parser.add_argument('--security2', action='store_true', help='Copy from the kernel security PPA2')
parser.add_argument('--esm', '-E', action='store_true', help='Copy from the kernel ESM PPA and to the kernel ESM proposed PPA') parser.add_argument('--esm', '-E', action='store_true', help='Copy from the kernel ESM PPA and to the kernel ESM proposed PPA')
parser.add_argument('--fips', action='store_true', help='Copy from the kernel FIPS PPA and to the kernel FIPS proposed PPA')
parser.add_argument('--ibmgt', action='store_true', help='Copy from the kernel IBM-GT build PPA to the corresponding proposed PPA')
parser.add_argument('--no-auto', action='store_true', help='Turn off automatic detection of ESM et al based on series') parser.add_argument('--no-auto', action='store_true', help='Turn off automatic detection of ESM et al based on series')
parser.add_argument('--to-signing', action='store_true', help='Copy from the kernel ESM/FIPS PPA to the ESM/FIPS signing PPA')
parser.add_argument('--from-signing', action='store_true', help='Copy from the ESM/FIPS signing PPA to the ESM/FIPS proposed PPA')
parser.add_argument('series', action='store', help='The series the source package is in') parser.add_argument('series', action='store', help='The series the source package is in')
parser.add_argument('source', action='store', help='The source package name') parser.add_argument('source', action='store', nargs='+', help='The source package name')
args = parser.parse_args() args = parser.parse_args()
to = 'ubuntu' if args.esm or args.fips or args.ibmgt:
ppa_name = '~canonical-kernel-team/ubuntu/ppa' print("NOTE: flags --esm, --fips, and --ibmgt are now deprecated")
security = False
# If we are allowed to intuit destinations do so:
# 1) precise is now destined for the ESM PPAs
if not args.no_auto:
if args.series == 'precise' and not args.esm:
print("NOTE: directing copy from and to ESM for precise")
args.esm = True
if args.esm:
ppa_name = '~canonical-kernel-esm/ubuntu/ppa'
to = '~canonical-kernel-esm/ubuntu/proposed'
to_pocket = 'release'
if args.security:
ppa_name = '~canonical-kernel-security-team/ubuntu/ppa'
if not args.esm:
security = True
else:
ppa_name = '~canonical-kernel-security-team/ubuntu/esm'
if args.security2:
ppa_name = '~canonical-kernel-security-team/ubuntu/ppa2'
if not args.esm:
security = True
(release, pkg) = (args.series, args.source) release = args.series
ks = KernelSeries()
launchpad = Launchpad.login_with( launchpad = Launchpad.login_with(
'ubuntu-archive-tools', 'production', version='devel') 'ubuntu-archive-tools', 'production', version='devel')
ubuntu = launchpad.distributions['ubuntu'] ubuntu = launchpad.distributions['ubuntu']
distro_series = ubuntu.getSeries(name_or_version=release) distro_series = ubuntu.getSeries(name_or_version=release)
kernel_ppa = launchpad.archives.getByReference(
reference=ppa_name) copies = []
for pkg in list(args.source):
# get current version in PPA for that series # BODGE: routing should just take release/pkg.
versions = kernel_ppa.getPublishedSources( args.source = pkg
source_name=pkg, exact_match=True, status='Published', pocket='Release',
distro_series=distro_series) (from_archive, to_archive, security) = routing(args, ks)
assert versions.total_size == 1 ##print("from_archive<{}> to_archive<{}>".format(from_archive, to_archive))
version = versions[0].source_package_version
if from_archive is None:
include_binaries = (pkg not in ('debian-installer') print("ERROR: bad source PPA")
and not pkg.startswith('linux-signed')) sys.exit(1)
if to_archive is None:
# Grab a reference to the 'to' archive and select a pocket. print("ERROR: bad destination")
to_archive = launchpad.archives.getByReference(reference=to) sys.exit(1)
if to == 'ubuntu':
to_pocket = 'proposed' (from_reference, from_pocket) = from_archive
else: (to_reference, to_pocket) = to_archive
to_pocket = 'release'
# Grab a reference to the 'from' archive.
print("""Copying {}/{}: from_archive = launchpad.archives.getByReference(
From: {} release reference=from_reference)
To: {} {}""".format(pkg, version, kernel_ppa, to_archive, to_pocket))
# Grab a reference to the 'to' archive.
to_archive = launchpad.archives.getByReference(reference=to_reference)
# get current version in PPA for that series
versions = from_archive.getPublishedSources(
source_name=pkg, exact_match=True, status='Published', pocket=from_pocket,
distro_series=distro_series)
version = None
if versions.total_size == 1:
version = versions[0].source_package_version
include_binaries = (pkg not in ('debian-installer')
and not pkg.startswith('linux-signed'))
if args.from_signing:
include_binaries = True
print("""Copying {}/{}:
From: {} {}
To: {} {}
Binaries: {}""".format(pkg, version, from_archive.reference, from_pocket, to_archive.reference, to_pocket, include_binaries))
if not version:
print("ERROR: no version to copy")
sys.exit(1)
copies.append({
'from_archive': from_archive,
'include_binaries': include_binaries,
'source_name': pkg,
'to_series': release,
'to_pocket': to_pocket,
'version': version,
'auto_approve': True,
'unembargo': security,
})
if args.dry_run: if args.dry_run:
print("Dry run; no packages copied.") print("Dry run; no packages copied.")
sys.exit(0) sys.exit(0)
# Finally ready to actually copy this. for copy in copies:
to_archive.copyPackage( # We found valid packages for each requested element, actually copy them.
from_archive=kernel_ppa, include_binaries=include_binaries, to_archive.copyPackage(**copy)
source_name=pkg, to_series=release, to_pocket=to_pocket, version=version,
auto_approve=True, unembargo=security)
# TODO: adjust this script to use find-bin-overrides or rewrite # TODO: adjust this script to use find-bin-overrides or rewrite
# find-bin-overrides to use lpapi and use it here. # find-bin-overrides to use lpapi and use it here.

@ -1,10 +1,10 @@
#! /usr/bin/env python #! /usr/bin/env python2.7
from __future__ import print_function from __future__ import print_function
import atexit import atexit
import bz2
from collections import namedtuple from collections import namedtuple
import gzip
import optparse import optparse
import os import os
import re import re
@ -13,12 +13,13 @@ import subprocess
import tempfile import tempfile
try: try:
from urllib.parse import unquote from urllib.parse import unquote
from urllib.request import urlretrieve
except ImportError: except ImportError:
from urllib import unquote, urlretrieve from urllib import unquote
import apt_pkg import apt_pkg
from launchpadlib.launchpad import Launchpad from launchpadlib.launchpad import Launchpad
import lzma
import requests
# from dak, more or less # from dak, more or less
@ -44,18 +45,42 @@ def ensure_tempdir():
def decompress_open(tagfile): def decompress_open(tagfile):
if tagfile.startswith('http:') or tagfile.startswith('ftp:'): if tagfile.startswith('http:') or tagfile.startswith('ftp:'):
url = tagfile
tagfile = urlretrieve(url)[0]
if tagfile.endswith('.gz'):
ensure_tempdir() ensure_tempdir()
decompressed = tempfile.mktemp(dir=tempdir) response = requests.get(tagfile, stream=True)
fin = gzip.GzipFile(filename=tagfile) if response.status_code == 404:
with open(decompressed, 'wb') as fout: response.close()
fout.write(fin.read()) tagfile = tagfile.replace('.xz', '.bz2')
return open(decompressed, 'r') response = requests.get(tagfile, stream=True)
response.raise_for_status()
if '.' in tagfile:
suffix = '.' + tagfile.rsplit('.', 1)[1]
else:
suffix = ''
fd, tagfile = tempfile.mkstemp(suffix=suffix, dir=tempdir)
with os.fdopen(fd, 'wb') as f:
f.write(response.raw.read())
response.close()
elif not os.path.exists(tagfile):
tagfile = tagfile.replace('.xz', '.bz2')
if tagfile.endswith('.xz'):
decompressor = lzma.LZMAFile
elif tagfile.endswith('.bz2'):
decompressor = bz2.BZ2File
else:
decompressor = None
if decompressor is not None:
fd, decompressed = tempfile.mkstemp(dir=tempdir)
dcf = decompressor(tagfile)
try:
with os.fdopen(fd, 'wb') as f:
f.write(dcf.read())
finally:
dcf.close()
return open(decompressed, 'rb')
else: else:
return open(tagfile, 'r') return open(tagfile, 'rb')
Section = namedtuple("Section", ["version", "directory", "files"]) Section = namedtuple("Section", ["version", "directory", "files"])
@ -84,7 +109,11 @@ def find_dsc(options, pkg, section):
filenames = [] filenames = []
for url in spph.sourceFileUrls(): for url in spph.sourceFileUrls():
filename = os.path.join(outdir, unquote(os.path.basename(url))) filename = os.path.join(outdir, unquote(os.path.basename(url)))
urlretrieve(url, filename) response = requests.get(url, stream=True)
response.raise_for_status()
with open(filename, 'wb') as f:
f.write(response.raw.read())
response.close()
filenames.append(filename) filenames.append(filename)
yield [s for s in filenames if s.endswith('.dsc')][0] yield [s for s in filenames if s.endswith('.dsc')][0]
@ -132,7 +161,8 @@ def descended_from(options, pkg, section1, section2):
for dsc in find_dsc(options, pkg, section1): for dsc in find_dsc(options, pkg, section1):
try: try:
versions = get_changelog_versions(pkg, dsc, section1.version) versions = get_changelog_versions(pkg, dsc, section1.version)
except BrokenSourcePackage as exception: except BrokenSourcePackage as e:
exception = e
continue continue
return section1.version in versions return section1.version in versions
raise exception raise exception
@ -223,9 +253,9 @@ def main():
for suite in suites: for suite in suites:
for component in 'main', 'restricted', 'universe', 'multiverse': for component in 'main', 'restricted', 'universe', 'multiverse':
tagfile1 = '%s/dists/%s-security/%s/source/Sources.gz' % ( tagfile1 = '%s/dists/%s-security/%s/source/Sources.xz' % (
options.mirrors[0], suite, component) options.mirrors[0], suite, component)
tagfile2 = '%s/dists/%s-updates/%s/source/Sources.gz' % ( tagfile2 = '%s/dists/%s-updates/%s/source/Sources.xz' % (
options.mirrors[0], suite, component) options.mirrors[0], suite, component)
name1 = '%s-security' % suite name1 = '%s-security' % suite
name2 = '%s-updates' % suite name2 = '%s-updates' % suite

@ -20,7 +20,7 @@ set -e
MIRROR=$HOME/mirror MIRROR=$HOME/mirror
DISTRIBUTION="${DISTRIBUTION:-ubuntu}" DISTRIBUTION="${DISTRIBUTION:-ubuntu}"
RELEASE="${RELEASE:-disco}" RELEASE="${RELEASE:-groovy}"
OUTDIR="${OUTDIR:-$HOME/public_html/NBS}" OUTDIR="${OUTDIR:-$HOME/public_html/NBS}"
OUTFILE="${OUTFILE:-$HOME/public_html/nbs.html}" OUTFILE="${OUTFILE:-$HOME/public_html/nbs.html}"
@ -42,6 +42,6 @@ checkrdepends -B "$MIRROR/$DISTRIBUTION" -s $RELEASE -b -d "$D" $CHECK
rsync -a --delete "$D/" "$OUTDIR/" rsync -a --delete "$D/" "$OUTDIR/"
nbs-report -d "$DISTRIBUTION" -s "$RELEASE" --csv "${OUTFILE%.html}.csv" \ nbs-report -B "$MIRROR/$DISTRIBUTION" -d "$DISTRIBUTION" -s "$RELEASE" --csv "${OUTFILE%.html}.csv" \
"$OUTDIR/" >"$OUTFILE.new" && \ "$OUTDIR/" >"$OUTFILE.new" && \
mv "$OUTFILE.new" "$OUTFILE" mv "$OUTFILE.new" "$OUTFILE"

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python3
# Copyright (C) 2013 Canonical Ltd. # Copyright (C) 2013 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python2.7
# Copyright (C) 2014 Canonical Ltd. # Copyright (C) 2014 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2008, 2009, 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2008, 2009, 2010, 2011, 2012 Canonical Ltd.
@ -24,7 +24,7 @@
from __future__ import print_function from __future__ import print_function
from optparse import OptionParser, SUPPRESS_HELP import argparse
import sys import sys
import launchpadlib.errors import launchpadlib.errors
@ -74,44 +74,45 @@ def multiline_input(prompt):
buf.append(line) buf.append(line)
def get_archive(options, launchpad): def get_archive(args, launchpad):
# We default to looking up by archive reference (ubuntu, # We default to looking up by archive reference (ubuntu,
# ubuntu/partner or ~owner/ubuntu/ppa). # ubuntu/partner or ~owner/ubuntu/ppa).
if options.archive is not None: if args.archive is not None:
archive = launchpad.archives.getByReference(reference=options.archive) archive = launchpad.archives.getByReference(reference=args.archive)
if archive is not None: if archive is not None:
return archive return archive
# But we also still support combining a distro name in -d and an # But we also still support combining a distro name in -d and an
# archive name or old PPA reference in -A (-d ubuntu, # archive name or old PPA reference in -A (-d ubuntu,
# -d ubuntu -A partner, or -d ubuntu -A owner/ppa). # -d ubuntu -A partner, or -d ubuntu -A owner/ppa).
distro = launchpad.distributions[options.distro] distro = launchpad.distributions[args.distro]
if options.archive is None: if args.archive is None:
return distro.main_archive return distro.main_archive
else: else:
if '/' in options.archive: if '/' in args.archive:
owner, ppa_name = options.archive.split('/') owner, ppa_name = args.archive.split('/')
return launchpad.people[owner].getPPAByName( return launchpad.people[owner].getPPAByName(
distribution=distro, name=ppa_name) distribution=distro, name=ppa_name)
for archive in distro.archives: for archive in distro.archives:
if archive.name == options.archive: if archive.name == args.archive:
return archive return archive
raise AssertionError("No such archive in Ubuntu: %s" % options.archive) raise AssertionError("No such archive in Ubuntu: %s" % args.archive)
def get_source_components(options, launchpad, archive, source): def get_source_components(args, launchpad, archive, source):
try: try:
from debian import debian_support from debian import debian_support
except ImportError: except ImportError:
from debian_bundle import debian_support from debian_bundle import debian_support
args = {} kwargs = {}
if options.series: if args.series:
args['distro_series'] = options.series kwargs['distro_series'] = args.series
newest = {} newest = {}
for spph in archive.getPublishedSources( for spph in archive.getPublishedSources(
source_name=source, exact_match=True, status='Published', **args): source_name=source, exact_match=True, status='Published',
**kwargs):
if not spph.distro_series.active: if not spph.distro_series.active:
continue continue
new_version = debian_support.Version(spph.source_package_version) new_version = debian_support.Version(spph.source_package_version)
@ -128,15 +129,15 @@ permission_names = dict(upload='Archive Upload Rights',
admin='Queue Administration Rights') admin='Queue Administration Rights')
def do_query(options): def do_query(args):
"""Query existing permissions and show on stdout.""" """Query existing permissions and show on stdout."""
if options.archive.self_link == options.distro.main_archive_link: if args.archive.self_link == args.distro.main_archive_link:
archives = options.distro.archives archives = args.distro.archives
else: else:
archives = [options.archive] archives = [args.archive]
if options.person: if args.person:
for person in options.person: for person in args.person:
if '@' in person: if '@' in person:
lp_person = launchpad.people.getByEmail(email=person) lp_person = launchpad.people.getByEmail(email=person)
else: else:
@ -149,29 +150,29 @@ def do_query(options):
for archive in archives: for archive in archives:
perms.extend(archive.getPermissionsForPerson( perms.extend(archive.getPermissionsForPerson(
person=lp_person)) person=lp_person))
if options.acl_type: if args.acl_type:
perm_name = permission_names[options.acl_type] perm_name = permission_names[args.acl_type]
perms = [p for p in perms if p.permission == perm_name] perms = [p for p in perms if p.permission == perm_name]
print("== All rights for %s ==" % lp_person.name) print("== All rights for %s ==" % lp_person.name)
print_perms(perms, options.series) print_perms(perms, args.series)
if options.component: if args.component:
perms = [] perms = []
if not options.acl_type or options.acl_type == 'upload': if not args.acl_type or args.acl_type == 'upload':
for archive in archives: for archive in archives:
perms.extend(archive.getUploadersForComponent( perms.extend(archive.getUploadersForComponent(
component_name=options.component)) component_name=args.component))
if not options.acl_type or options.acl_type == 'admin': if not args.acl_type or args.acl_type == 'admin':
for archive in archives: for archive in archives:
perms.extend(archive.getQueueAdminsForComponent( perms.extend(archive.getQueueAdminsForComponent(
component_name=options.component)) component_name=args.component))
print("== All rights for component '%s' ==" % options.component) print("== All rights for component '%s' ==" % args.component)
print_perms(perms, options.series) print_perms(perms, args.series)
if options.packageset: if args.packageset:
for packageset in options.packageset: for packageset in args.packageset:
lp_set = launchpad.packagesets.getByName( lp_set = launchpad.packagesets.getByName(
name=packageset, distroseries=options.series) name=packageset, distroseries=args.series)
perms = [] perms = []
for archive in archives: for archive in archives:
@ -179,27 +180,27 @@ def do_query(options):
packageset=lp_set)) packageset=lp_set))
print(("== All uploaders for package set '%s' in '%s' " print(("== All uploaders for package set '%s' in '%s' "
"(owned by '%s') ==" % "(owned by '%s') ==" %
(packageset, options.series.name, (packageset, args.series.name,
lp_set.owner.display_name))) lp_set.owner.display_name)))
print_perms(perms, options.series) print_perms(perms, args.series)
sources = sorted(lp_set.getSourcesIncluded(direct_inclusion=True)) sources = sorted(lp_set.getSourcesIncluded(direct_inclusion=True))
if sources: if sources:
print() print()
print("== All source packages in package set '%s' " print("== All source packages in package set '%s' "
"in '%s' ==" % (packageset, options.series.name)) "in '%s' ==" % (packageset, args.series.name))
for source in sources: for source in sources:
print(source) print(source)
child_sets = list(lp_set.setsIncluded(direct_inclusion=True)) child_sets = list(lp_set.setsIncluded(direct_inclusion=True))
if child_sets: if child_sets:
print() print()
print("== All package sets in package set '%s' in '%s' ==" % print("== All package sets in package set '%s' in '%s' ==" %
(packageset, options.series.name)) (packageset, args.series.name))
for child_set in child_sets: for child_set in child_sets:
print(child_set.name) print(child_set.name)
if options.source: if args.source:
for source in options.source: for source in args.source:
perms = [] perms = []
perms_set = [] perms_set = []
for archive in archives: for archive in archives:
@ -208,63 +209,62 @@ def do_query(options):
perms_set.extend(archive.getPackagesetsForSource( perms_set.extend(archive.getPackagesetsForSource(
sourcepackagename=source)) sourcepackagename=source))
print("== All uploaders for package '%s' ==" % source) print("== All uploaders for package '%s' ==" % source)
print_perms(perms, options.series) print_perms(perms, args.series)
print_perms(perms_set, options.series) print_perms(perms_set, args.series)
for archive in archives: for archive in archives:
for series, component in get_source_components( for series, component in get_source_components(
options, launchpad, archive, source): args, launchpad, archive, source):
perms_component = archive.getUploadersForComponent( perms_component = archive.getUploadersForComponent(
component_name=component) component_name=component)
print_perms(perms_component, series=series) print_perms(perms_component, series=series)
if options.pocket: if args.pocket:
perms = [] perms = []
if not options.acl_type or options.acl_type == 'upload': if not args.acl_type or args.acl_type == 'upload':
for archive in archives: for archive in archives:
perms.extend(archive.getUploadersForPocket( perms.extend(archive.getUploadersForPocket(pocket=args.pocket))
pocket=options.pocket)) if not args.acl_type or args.acl_type == 'admin':
if not options.acl_type or options.acl_type == 'admin':
for archive in archives: for archive in archives:
perms.extend(archive.getQueueAdminsForPocket( perms.extend(archive.getQueueAdminsForPocket(
pocket=options.pocket)) pocket=args.pocket))
print("== All rights for pocket '%s' ==" % options.pocket) print("== All rights for pocket '%s' ==" % args.pocket)
print_perms(perms, options.series) print_perms(perms, args.series)
if (not options.person and not options.component and if (not args.person and not args.component and
not options.packageset and not options.source and not args.packageset and not args.source and
not options.pocket): not args.pocket):
perms = [] perms = []
for archive in archives: for archive in archives:
perms.extend(archive.getAllPermissions()) perms.extend(archive.getAllPermissions())
if options.acl_type: if args.acl_type:
perm_name = permission_names[options.acl_type] perm_name = permission_names[args.acl_type]
perms = [p for p in perms if p.permission == perm_name] perms = [p for p in perms if p.permission == perm_name]
print("== All rights ==") print("== All rights ==")
print_perms(perms, options.series) print_perms(perms, args.series)
def validate_add_delete_options(options, requires_person=True): def validate_add_delete_options(args, requires_person=True):
if options.packageset and options.source: if args.packageset and args.source:
# Special options to manage package sets, bodged into this tool # Special options to manage package sets, bodged into this tool
# since they aren't entirely inconvenient here. # since they aren't entirely inconvenient here.
if options.component or options.person: if args.component or args.person:
print("-P <packageset> -s <source> cannot be used with a " print("-P <packageset> -s <source> cannot be used with a "
"component or person as well") "component or person as well")
return False return False
return True return True
if requires_person and not options.person: if requires_person and not args.person:
print("You must specify at least one person to (de-)authorise.") print("You must specify at least one person to (de-)authorise.")
return False return False
count = 0 count = 0
if options.component: if args.component:
count += 1 count += 1
if options.packageset: if args.packageset:
count += 1 count += 1
if options.source: if args.source:
count += 1 count += 1
if options.pocket: if args.pocket:
count += 1 count += 1
if count > 1: if count > 1:
print("You can only specify one of package set, source, component, " print("You can only specify one of package set, source, component, "
@ -279,94 +279,94 @@ def validate_add_delete_options(options, requires_person=True):
return True return True
def do_add(options): def do_add(args):
"""Add a new permission.""" """Add a new permission."""
if not validate_add_delete_options(options): if not validate_add_delete_options(args):
return False return False
if options.packageset and options.source: if args.packageset and args.source:
for packageset in options.packageset: for packageset in args.packageset:
lp_set = launchpad.packagesets.getByName( lp_set = launchpad.packagesets.getByName(
name=packageset, distroseries=options.series) name=packageset, distroseries=args.series)
lp_set.addSources(names=options.source) lp_set.addSources(names=args.source)
print("Added:") print("Added:")
for source in options.source: for source in args.source:
print(source) print(source)
return return
people = [launchpad.people[person] for person in options.person] people = [launchpad.people[person] for person in args.person]
if options.source: if args.source:
for source in options.source: for source in args.source:
for person in people: for person in people:
perm = options.archive.newPackageUploader( perm = args.archive.newPackageUploader(
person=person, source_package_name=source) person=person, source_package_name=source)
print("Added:") print("Added:")
print_perms([perm]) print_perms([perm])
return return
if options.packageset: if args.packageset:
for packageset in options.packageset: for packageset in args.packageset:
lp_set = launchpad.packagesets.getByName( lp_set = launchpad.packagesets.getByName(
name=packageset, distroseries=options.series) name=packageset, distroseries=args.series)
for person in people: for person in people:
perm = options.archive.newPackagesetUploader( perm = args.archive.newPackagesetUploader(
person=person, packageset=lp_set) person=person, packageset=lp_set)
print("Added:") print("Added:")
print_perms([perm]) print_perms([perm])
return return
if options.component: if args.component:
for person in people: for person in people:
if not options.acl_type or options.acl_type == 'upload': if not args.acl_type or args.acl_type == 'upload':
perm = options.archive.newComponentUploader( perm = args.archive.newComponentUploader(
person=person, component_name=options.component) person=person, component_name=args.component)
else: else:
perm = options.archive.newQueueAdmin( perm = args.archive.newQueueAdmin(
person=person, component_name=options.component) person=person, component_name=args.component)
print("Added:") print("Added:")
print_perms([perm]) print_perms([perm])
return return
if options.pocket: if args.pocket:
admin_kwargs = {} admin_kwargs = {}
if options.series: if args.series:
admin_kwargs["distroseries"] = options.series admin_kwargs["distroseries"] = args.series
for person in people: for person in people:
if not options.acl_type or options.acl_type == 'upload': if not args.acl_type or args.acl_type == 'upload':
perm = options.archive.newPocketUploader( perm = args.archive.newPocketUploader(
person=person, pocket=options.pocket) person=person, pocket=args.pocket)
else: else:
perm = options.archive.newPocketQueueAdmin( perm = args.archive.newPocketQueueAdmin(
person=person, pocket=options.pocket, **admin_kwargs) person=person, pocket=args.pocket, **admin_kwargs)
print("Added:") print("Added:")
print_perms([perm]) print_perms([perm])
return return
def do_delete(options): def do_delete(args):
"""Delete a permission.""" """Delete a permission."""
# We kind of hacked packageset management into here. # We kind of hacked packageset management into here.
# Deleting packagesets doesn't require a person... # Deleting packagesets doesn't require a person...
requires_person = not (options.packageset and not options.source) requires_person = not (args.packageset and not args.source)
if not validate_add_delete_options(options, requires_person): if not validate_add_delete_options(args, requires_person):
return False return False
if options.packageset and options.source: if args.packageset and args.source:
for packageset in options.packageset: for packageset in args.packageset:
lp_set = launchpad.packagesets.getByName( lp_set = launchpad.packagesets.getByName(
name=packageset, distroseries=options.series) name=packageset, distroseries=args.series)
lp_set.removeSources(names=options.source) lp_set.removeSources(names=args.source)
print("Deleted:") print("Deleted:")
for source in options.source: for source in args.source:
print(source) print(source)
return return
if options.packageset and not options.person: if args.packageset and not args.person:
for packageset in options.packageset: for packageset in args.packageset:
lp_set = launchpad.packagesets.getByName( lp_set = launchpad.packagesets.getByName(
name=packageset, distroseries=options.series) name=packageset, distroseries=args.series)
uploaders = options.archive.getUploadersForPackageset( uploaders = args.archive.getUploadersForPackageset(
direct_permissions=True, packageset=lp_set) direct_permissions=True, packageset=lp_set)
if len(uploaders) > 0: if len(uploaders) > 0:
print("Cannot delete packageset with defined uploaders") print("Cannot delete packageset with defined uploaders")
@ -386,82 +386,82 @@ def do_delete(options):
ack = input("Remove? (y/N): ") ack = input("Remove? (y/N): ")
if ack.lower() == 'y': if ack.lower() == 'y':
lp_set.lp_delete() lp_set.lp_delete()
print("Deleted %s/%s" % (lp_set.name, options.series.name)) print("Deleted %s/%s" % (lp_set.name, args.series.name))
return return
lp_people = [launchpad.people[person] for person in options.person] lp_people = [launchpad.people[person] for person in args.person]
if options.source: if args.source:
for source in options.source: for source in args.source:
for lp_person in lp_people: for lp_person in lp_people:
try: try:
options.archive.deletePackageUploader( args.archive.deletePackageUploader(
person=lp_person, source_package_name=source) person=lp_person, source_package_name=source)
print("Deleted %s/%s" % (lp_person.name, source)) print("Deleted %s/%s" % (lp_person.name, source))
except Exception: except Exception:
print("Failed to delete %s/%s" % (lp_person.name, source)) print("Failed to delete %s/%s" % (lp_person.name, source))
return return
if options.packageset: if args.packageset:
for packageset in options.packageset: for packageset in args.packageset:
lp_set = launchpad.packagesets.getByName( lp_set = launchpad.packagesets.getByName(
name=packageset, distroseries=options.series) name=packageset, distroseries=args.series)
for lp_person in lp_people: for lp_person in lp_people:
options.archive.deletePackagesetUploader( args.archive.deletePackagesetUploader(
person=lp_person, packageset=lp_set) person=lp_person, packageset=lp_set)
print("Deleted %s/%s/%s" % (lp_person.name, packageset, print("Deleted %s/%s/%s" % (lp_person.name, packageset,
options.series.name)) args.series.name))
return return
if options.component: if args.component:
for lp_person in lp_people: for lp_person in lp_people:
if not options.acl_type or options.acl_type == 'upload': if not args.acl_type or args.acl_type == 'upload':
options.archive.deleteComponentUploader( args.archive.deleteComponentUploader(
person=lp_person, component_name=options.component) person=lp_person, component_name=args.component)
print("Deleted %s/%s" % (lp_person.name, options.component)) print("Deleted %s/%s" % (lp_person.name, args.component))
else: else:
options.archive.deleteQueueAdmin( args.archive.deleteQueueAdmin(
person=lp_person, component_name=options.component) person=lp_person, component_name=args.component)
print("Deleted %s/%s (admin)" % (lp_person.name, print("Deleted %s/%s (admin)" % (lp_person.name,
options.component)) args.component))
return return
if options.pocket: if args.pocket:
admin_kwargs = {} admin_kwargs = {}
if options.series: if args.series:
admin_kwargs["distroseries"] = options.series admin_kwargs["distroseries"] = args.series
for lp_person in lp_people: for lp_person in lp_people:
if not options.acl_type or options.acl_type == 'upload': if not args.acl_type or args.acl_type == 'upload':
options.archive.deletePocketUploader( args.archive.deletePocketUploader(
person=lp_person, pocket=options.pocket) person=lp_person, pocket=args.pocket)
print("Deleted %s/%s" % (lp_person.name, options.pocket)) print("Deleted %s/%s" % (lp_person.name, args.pocket))
else: else:
options.archive.deletePocketQueueAdmin( args.archive.deletePocketQueueAdmin(
person=lp_person, pocket=options.pocket, **admin_kwargs) person=lp_person, pocket=args.pocket, **admin_kwargs)
if options.series: if args.series:
print( print(
"Deleted %s/%s/%s (admin)" % "Deleted %s/%s/%s (admin)" %
(lp_person.name, options.pocket, options.series.name)) (lp_person.name, args.pocket, args.series.name))
else: else:
print("Deleted %s/%s (admin)" % print("Deleted %s/%s (admin)" %
(lp_person.name, options.pocket)) (lp_person.name, args.pocket))
return return
def do_create(options): def do_create(args):
if not options.packageset: if not args.packageset:
print("You can only create a package set, not something else.") print("You can only create a package set, not something else.")
return False return False
if not options.person or len(options.person) != 1: if not args.person or len(args.person) != 1:
print("You must specify exactly one person to own the new package " print("You must specify exactly one person to own the new package "
"set.") "set.")
return False return False
distro_series = options.series or options.distro.current_series distro_series = args.series or args.distro.current_series
lp_person = launchpad.people[options.person[0]] lp_person = launchpad.people[args.person[0]]
for packageset in options.packageset: for packageset in args.packageset:
try: try:
if launchpad.packagesets.getByName( if launchpad.packagesets.getByName(
name=packageset, distroseries=distro_series): name=packageset, distroseries=distro_series):
@ -477,22 +477,22 @@ def do_create(options):
print(ps) print(ps)
def do_modify(options): def do_modify(args):
if not options.packageset: if not args.packageset:
print("You can only modify a package set, not something else.") print("You can only modify a package set, not something else.")
return False return False
if options.person and len(options.person) > 1: if args.person and len(args.person) > 1:
print("You can only specify one person as the new packageset owner.") print("You can only specify one person as the new packageset owner.")
return False return False
distro_series = options.series or options.distro.current_series distro_series = args.series or args.distro.current_series
lp_person = None lp_person = None
if options.person: if args.person:
lp_person = launchpad.people[options.person[0]] lp_person = launchpad.people[args.person[0]]
for packageset in options.packageset: for packageset in args.packageset:
lp_set = launchpad.packagesets.getByName( lp_set = launchpad.packagesets.getByName(
name=packageset, distroseries=distro_series) name=packageset, distroseries=distro_series)
if lp_person: if lp_person:
@ -521,22 +521,22 @@ def do_modify(options):
continue continue
def do_copy(options): def do_copy(args):
if options.archive.self_link == options.distro.main_archive_link: if args.archive.self_link == args.distro.main_archive_link:
archives = options.distro.archives archives = args.distro.archives
else: else:
archives = [options.archive] archives = [args.archive]
if not options.packageset: if not args.packageset:
print("You can only copy a package set, not something else.") print("You can only copy a package set, not something else.")
return False return False
distro_series = options.series or options.distro.current_series distro_series = args.series or args.distro.current_series
dst = input("Name of the destination series: ") dst = input("Name of the destination series: ")
dst_series = options.distro.getSeries(name_or_version=dst) dst_series = args.distro.getSeries(name_or_version=dst)
for packageset in options.packageset: for packageset in args.packageset:
src_pkgset = launchpad.packagesets.getByName( src_pkgset = launchpad.packagesets.getByName(
name=packageset, distroseries=distro_series) name=packageset, distroseries=distro_series)
if not src_pkgset: if not src_pkgset:
@ -560,27 +560,27 @@ def do_copy(options):
person=perm.person_link, packageset=ps) person=perm.person_link, packageset=ps)
def do_check(options): def do_check(args):
"""Check if a person can upload a package.""" """Check if a person can upload a package."""
if not options.person: if not args.person:
print("A person needs to be specified to check.") print("A person needs to be specified to check.")
return False return False
if not options.source: if not args.source:
print("A source package needs to be specified to check.") print("A source package needs to be specified to check.")
return False return False
people = [launchpad.people[person] for person in options.person] people = [launchpad.people[person] for person in args.person]
distro_series = options.series or options.distro.current_series distro_series = args.series or args.distro.current_series
if options.pocket: if args.pocket:
pocket = options.pocket pocket = args.pocket
else: else:
pocket = 'Release' pocket = 'Release'
for person in people: for person in people:
for srcpkg in options.source: for srcpkg in args.source:
try: try:
spph = options.archive.getPublishedSources( spph = args.archive.getPublishedSources(
distro_series=distro_series, distro_series=distro_series,
exact_match=True, exact_match=True,
pocket=pocket, pocket=pocket,
@ -588,10 +588,10 @@ def do_check(options):
status='Published', status='Published',
)[0] )[0]
except IndexError: except IndexError:
if not options.pocket: if not args.pocket:
raise raise
# Not yet in options.pocket, but maybe in Release? # Not yet in args.pocket, but maybe in Release?
spph = options.archive.getPublishedSources( spph = args.archive.getPublishedSources(
distro_series=distro_series, distro_series=distro_series,
exact_match=True, exact_match=True,
pocket='Release', pocket='Release',
@ -599,7 +599,7 @@ def do_check(options):
status='Published', status='Published',
)[0] )[0]
try: try:
options.archive.checkUpload( args.archive.checkUpload(
component=spph.component_name, component=spph.component_name,
distroseries=distro_series, distroseries=distro_series,
person=person, person=person,
@ -619,99 +619,98 @@ def do_check(options):
print(e.content) print(e.content)
def main(options, action): def main(args):
if action == "query": if args.action == "query":
do_query(options) do_query(args)
elif action == "add": elif args.action == "add":
do_add(options) do_add(args)
elif action in ("delete", "remove"): elif args.action in ("delete", "remove"):
do_delete(options) do_delete(args)
elif action == "create": elif args.action == "create":
do_create(options) do_create(args)
elif action == "modify": elif args.action == "modify":
do_modify(options) do_modify(args)
elif action == "copy": elif args.action == "copy":
do_copy(options) do_copy(args)
elif action == "check": elif args.action == "check":
do_check(options) do_check(args)
else: else:
raise AssertionError("Invalid action %s" % action) raise AssertionError("Invalid action %s" % args.action)
return return
if __name__ == '__main__': if __name__ == '__main__':
parser = OptionParser( parser = argparse.ArgumentParser(
usage="usage: %prog [options] " usage="%(prog)s [options] "
"query|add|delete|create|modify|copy|check", "query|add|delete|create|modify|copy|check",
epilog=lputils.ARCHIVE_REFERENCE_DESCRIPTION) epilog=lputils.ARCHIVE_REFERENCE_DESCRIPTION)
parser.add_option( parser.add_argument(
"-l", "--launchpad", dest="launchpad_instance", default="production") "-l", "--launchpad", dest="launchpad_instance", default="production")
parser.add_option("-A", "--archive", dest="archive") parser.add_argument("-A", "--archive", dest="archive")
parser.add_option("-S", "--series", dest="series") parser.add_argument("-S", "--series", dest="series")
parser.add_option("-p", "--person", dest="person", action="append") parser.add_argument("-p", "--person", dest="person", action="append")
parser.add_option("-c", "--component", dest="component") parser.add_argument("-c", "--component", dest="component")
parser.add_option("-P", "--packageset", dest="packageset", action="append") parser.add_argument(
parser.add_option("-s", "--source", dest="source", action="append") "-P", "--packageset", dest="packageset", action="append")
parser.add_option("--pocket", dest="pocket") parser.add_argument("-s", "--source", dest="source", action="append")
parser.add_option("-t", "--acl-type", dest="acl_type", parser.add_argument("--pocket", dest="pocket")
help="ACL type: upload or admin") parser.add_argument(
parser.add_option("--anon", dest="anon_login", action="store_true", "-t", "--acl-type", dest="acl_type", help="ACL type: upload or admin")
default=False, help="Login anonymously to Launchpad") parser.add_argument(
"--anon", dest="anon_login", action="store_true", default=False,
help="Login anonymously to Launchpad")
parser.add_argument(
"action",
choices=(
"query", "add", "delete", "create", "modify", "copy", "check"),
help="action to perform")
# Deprecated in favour of -A. # Deprecated in favour of -A.
parser.add_option( parser.add_argument(
"-d", "--distribution", dest="distro", default="ubuntu", "-d", "--distribution", dest="distro", default="ubuntu",
help=SUPPRESS_HELP) help=argparse.SUPPRESS)
options, args = parser.parse_args() args = parser.parse_args()
possible_actions = ('query', 'add', 'delete', 'create', 'copy', 'check') if args.anon_login and args.action not in ('query', 'check'):
if len(args) != 1:
parser.error(
"You must specify an action, one of:\n%s" %
", ".join(possible_actions))
if options.anon_login and args[0] not in ('query', 'check'):
print("E: Anonymous login not supported for this action.") print("E: Anonymous login not supported for this action.")
sys.exit(1) sys.exit(1)
if (args[0] != 'query' and if (args.action != 'query' and
not options.person and not options.component and not args.person and not args.component and
not options.packageset and not options.source and not args.packageset and not args.source and
not options.pocket): not args.pocket):
parser.error("Provide at least one of " parser.error("Provide at least one of "
"person/component/packageset/source/pocket") "person/component/packageset/source/pocket")
if options.packageset and not options.series: if args.packageset and not args.series:
parser.error("Package set requires an associated series") parser.error("Package set requires an associated series")
if options.acl_type and options.acl_type not in ('upload', 'admin'): if args.acl_type and args.acl_type not in ('upload', 'admin'):
parser.error("Invalid ACL type '%s' (valid: 'upload', 'admin')" % parser.error("Invalid ACL type '%s' (valid: 'upload', 'admin')" %
options.acl_type) args.acl_type)
if options.acl_type == 'admin' and options.packageset: if args.acl_type == 'admin' and args.packageset:
parser.error("ACL type admin not allowed for package sets") parser.error("ACL type admin not allowed for package sets")
if options.acl_type == 'admin' and options.source: if args.acl_type == 'admin' and args.source:
parser.error("ACL type admin not allowed for source packages") parser.error("ACL type admin not allowed for source packages")
if options.pocket: if args.pocket:
options.pocket = options.pocket.title() args.pocket = args.pocket.title()
if options.anon_login: if args.anon_login:
launchpad = Launchpad.login_anonymously( launchpad = Launchpad.login_anonymously(
CONSUMER_KEY, options.launchpad_instance, version="devel") CONSUMER_KEY, args.launchpad_instance, version="devel")
else: else:
launchpad = Launchpad.login_with( launchpad = Launchpad.login_with(
CONSUMER_KEY, options.launchpad_instance, version="devel") CONSUMER_KEY, args.launchpad_instance, version="devel")
options.archive = get_archive(options, launchpad) args.archive = get_archive(args, launchpad)
options.distro = options.archive.distribution args.distro = args.archive.distribution
if options.series is not None: if args.series is not None:
options.series = options.distro.getSeries( args.series = args.distro.getSeries(name_or_version=args.series)
name_or_version=options.series)
try: try:
main(options, args[0]) main(args)
except launchpadlib.errors.HTTPError as err: except launchpadlib.errors.HTTPError as err:
print("There was a %s error:" % err.response.status) print("There was a %s error:" % err.response.status)
print(err.content) print(err.content)

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python2.7
# Copyright 2012 Canonical Ltd. # Copyright 2012 Canonical Ltd.
# #
# This script will write update metrics for a given Ubuntu release in CSV # This script will write update metrics for a given Ubuntu release in CSV

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Copyright (C) 2011 Iain Lane # Copyright (C) 2011 Iain Lane
# Copyright (C) 2011 Stefano Rivera # Copyright (C) 2011 Stefano Rivera

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2012 Canonical Ltd. # Copyright (C) 2010, 2012 Canonical Ltd.

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python2.7
# Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd.

@ -46,20 +46,18 @@ def release_task_callback(lp, bugnum, task, context):
% bugnum) % bugnum)
return {} return {}
return {'proposed': task} return {'proposed': task}
if subtask == 'promote-to-updates': if subtask == 'promote-to-updates' and task.status in ('Confirmed', 'Fix Released'):
if task.status != 'Confirmed':
raise KernelWorkflowError(
"Ignoring bug %s, not ready to promote-to-updates"
% bugnum)
return {}
return {'updates': task} return {'updates': task}
if (subtask == 'promote-to-security' and task.status == 'Confirmed'): if subtask == 'promote-to-security' and task.status == 'Confirmed':
return {'security': task} return {'security': task}
return {} return {}
def release_source_callback(lp, bugnum, tasks, full_packages, release, context): def release_source_callback(lp, bugnum, tasks, full_packages, release, context):
if not 'proposed' in tasks or not 'updates' in tasks: if 'proposed' not in tasks or 'updates' not in tasks:
raise KernelWorkflowError()
if (tasks['updates'].status == 'Fix Released' and
'security' not in tasks):
raise KernelWorkflowError() raise KernelWorkflowError()
cmd = ['sru-release', '--no-bugs', release] cmd = ['sru-release', '--no-bugs', release]
cmd.extend(full_packages) cmd.extend(full_packages)
@ -71,10 +69,11 @@ def release_source_callback(lp, bugnum, tasks, full_packages, release, context):
print("Failed to run sru-release for %s" % bugnum) print("Failed to run sru-release for %s" % bugnum)
raise raise
tasks['updates'].status = 'Fix Committed' if 'updates' in tasks and tasks['updates'].status != 'Fix Released':
tasks['updates'].assignee = lp.me tasks['updates'].status = 'Fix Committed'
tasks['updates'].lp_save() tasks['updates'].assignee = lp.me
if 'security' in tasks: tasks['updates'].lp_save()
if 'security' in tasks and tasks['security'].status != 'Fix Released':
tasks['security'].status = 'Fix Committed' tasks['security'].status = 'Fix Committed'
tasks['security'].assignee = lp.me tasks['security'].assignee = lp.me
tasks['security'].lp_save() tasks['security'].lp_save()

@ -118,8 +118,16 @@ def generate_diff_from_master(me, archive, master_source, master_version,
sys.stdin.readline() sys.stdin.readline()
return return
# we need to pull in the master kernel into a separate directory as
# it might have the same name (flavor) as the one we are reviewing
master_dir = os.path.join(work_dir, 'master')
os.mkdir(master_dir)
# this is a bit ugly, since we actually have to chdir for a moment
# because dget has no option of declaring the output directory
os.chdir(master_dir)
fetch_tarball_from_cache( fetch_tarball_from_cache(
work_dir, tardir, master_source, master_upstream, start_dir) master_dir, tardir, master_source, master_upstream, start_dir)
# grab the old source first # grab the old source first
dget_cmd = ['dget', '-u', master_dsc] dget_cmd = ['dget', '-u', master_dsc]
@ -130,11 +138,14 @@ def generate_diff_from_master(me, archive, master_source, master_version,
(master_source, master_version)) (master_source, master_version))
raise e raise e
os.chdir(work_dir)
# generate the diff # generate the diff
master_path = os.path.join(master_dir, master_source)
print("Generating brief diff between new kernel and master (%s) to %s" % print("Generating brief diff between new kernel and master (%s) to %s" %
(master_version, os.path.join(work_dir, 'master_diff'))) (master_version, os.path.join(work_dir, 'master_diff')))
diff_cmd = ('diff -rq "{}-{}" "{}-{}" >master_diff').format( diff_cmd = ('diff -rq --label master "{}-{}" "{}-{}" >master_diff').format(
master_source, master_upstream, new_source, new_upstream) master_path, master_upstream, new_source, new_upstream)
subprocess.call(diff_cmd, shell=True) subprocess.call(diff_cmd, shell=True)
@ -168,12 +179,13 @@ def review_source_callback(lp, bugnum, tasks, full_packages, release, context):
master_version = None master_version = None
if context['diff']: if context['diff']:
master_source, master_version = get_master_kernel(lp, bugnum) master_source, master_version = get_master_kernel(lp, bugnum)
should_sign = any('-signed' in pkg for pkg in full_packages)
for source in full_packages: for source in full_packages:
process_source_package( process_source_package(
source, release, me, context['archive'], context['ppa'], source, release, me, context['archive'], context['ppa'],
context['ubuntu'], context['startdir'], context['workdir'], context['ubuntu'], context['startdir'], context['workdir'],
context['tardir'], context['esm'], context['tarcache'], context['tardir'], context['esm'], context['tarcache'],
master_source, master_version) master_source, master_version, should_sign)
tasks['proposed'].status = 'Fix Committed' tasks['proposed'].status = 'Fix Committed'
tasks['proposed'].assignee = me tasks['proposed'].assignee = me
tasks['proposed'].lp_save() tasks['proposed'].lp_save()
@ -222,7 +234,8 @@ def save_tarball_to_cache(directory, tardir, source, version):
def process_source_package(source, release, me, archive, ppa, ubuntu, def process_source_package(source, release, me, archive, ppa, ubuntu,
start_dir, work_dir, tardir, start_dir, work_dir, tardir,
esm=False, tar_cache=False, esm=False, tar_cache=False,
master_source=None, master_version=None): master_source=None, master_version=None,
should_sign=False):
series = ubuntu.getSeries(name_or_version=release) series = ubuntu.getSeries(name_or_version=release)
ppa_src = ppa.getPublishedSources(order_by_date=True, ppa_src = ppa.getPublishedSources(order_by_date=True,
@ -316,7 +329,8 @@ def process_source_package(source, release, me, archive, ppa, ubuntu,
save_tarball_to_cache(work_dir, tardir, source, new_upstream) save_tarball_to_cache(work_dir, tardir, source, new_upstream)
if (master_source and master_version and if (master_source and master_version and
'-meta' not in source and '-signed' not in source): '-meta' not in source and '-signed' not in source and
'-restricted-modules' not in source):
# if requested, we also generate a brief diff between the new kernel # if requested, we also generate a brief diff between the new kernel
# and its 'master' kernel # and its 'master' kernel
generate_diff_from_master( generate_diff_from_master(
@ -349,35 +363,55 @@ def process_source_package(source, release, me, archive, ppa, ubuntu,
(source, ppa_ver)) (source, ppa_ver))
raise e raise e
print("Accepted") print("Accepted")
# we only care about accepting signed bits if there is a -signed
# package in the handled sources and when we're not working with
# ESM (as those don't go through the queue)
if not should_sign or esm:
return
# we know this isn't a kernel package containing signed bits, # we know this isn't a kernel package containing signed bits,
# so don't subject ourselves to extra delays # so don't subject ourselves to extra delays
if '-meta' in source or '-signed' in source: if ('-meta' in source or '-signed' in source or
'-restricted-modules' in source):
return return
print("Checking for UEFI binaries") print("Checking for UEFI binaries in the Unapproved queue")
# Arbitrary 10 second delay, maybe enough to let uefi binaries hit
# the unapproved queue.
time.sleep(10)
# accept any related uefi binaries. We filter as closely as possible
# on name without hard-coding architecture, and we also filter to
# only include uefi binaries that have appeared since we started the
# copy to avoid accepting something that might have been improperly
# copied into the queue by an "attacker" with upload rights.
uefis = [] uefis = []
for signed_type in ('uefi', 'signing'): # we try looking for signed bits a few times after short, constant
uefis.extend(series.getPackageUploads( # delays. The binaries nowadays appear after some seconds, but
archive=archive['release'], # having a constant delay is suboptimal.
pocket='Proposed', for n in range(5):
status='Unapproved', time.sleep(3)
custom_type=signed_type, # accept any related uefi binaries. We filter as closely as
name='{}_{}_'.format(source, ppa_ver), # possible on name without hard-coding architecture, and we also
created_since_date=copy_time)) # filter to only include uefi binaries that have appeared since we
for uefi in uefis: # started the copy to avoid accepting something that might have
print("Accepting {}".format(uefi)) # been improperly copied into the queue by an "attacker" with
uefi.acceptFromQueue() # upload rights.
for signed_type in ('uefi', 'signing'):
uefis.extend(series.getPackageUploads(
archive=archive['release'],
pocket='Proposed',
status='Unapproved',
custom_type=signed_type,
name='{}_{}_'.format(source, ppa_ver),
created_since_date=copy_time))
if uefis:
for uefi in uefis:
print("Accepting {}".format(uefi))
uefi.acceptFromQueue()
break
else:
print("No UEFI binaries found after %s tries. Please manually "
"check for their existance and approve before accepting the "
"signed sources." % n)
print("Press enter to continue.")
sys.stdout.flush()
sys.stdin.readline()
if __name__ == '__main__': if __name__ == '__main__':
default_release = 'cosmic' default_release = 'focal'
parser = OptionParser( parser = OptionParser(
usage="Usage: %prog [options] bug [bug ...]") usage="Usage: %prog [options] bug [bug ...]")
@ -399,6 +433,9 @@ if __name__ == '__main__':
parser.add_option( parser.add_option(
"-d", "--diff-against-master", dest="diff_master", "-d", "--diff-against-master", dest="diff_master",
action="store_true") action="store_true")
parser.add_option(
"--skip-name-check", dest="nonamecheck",
action="store_true")
opts, bugs = parser.parse_args() opts, bugs = parser.parse_args()
@ -440,7 +477,8 @@ if __name__ == '__main__':
context = { context = {
'archive': archive, 'ppa': ppa, 'ubuntu': ubuntu, 'archive': archive, 'ppa': ppa, 'ubuntu': ubuntu,
'tardir': tardir, 'tarcache': opts.caching, 'startdir': start_dir, 'tardir': tardir, 'tarcache': opts.caching, 'startdir': start_dir,
'esm': opts.esm, 'diff': opts.diff_master 'esm': opts.esm, 'diff': opts.diff_master,
'skipnamecheck': opts.nonamecheck
} }
for bugnum in bugs: for bugnum in bugs:
with ExitStack() as resources: with ExitStack() as resources:

@ -0,0 +1,657 @@
#!/usr/bin/env python2.7
#
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
import os
import yaml
class KernelRoutingEntry:
def __init__(self, ks, source, data):
name = "{}:{}".format(source.series.codename, source.name)
if isinstance(data, str):
name = data
table = source.series.routing_table
if table is None:
raise ValueError("unable to map routing alias {}, "
"no series routing table".format(data))
if data not in table:
raise ValueError("unable to map routing alias {}, "
"not listed in series routing table".format(data))
data = table[data]
# Clear out any entries that have been overriden to None.
for entry in dict(data):
if data[entry] is None:
del data[entry]
self._ks = ks
self._source = source
self._name = name
self._data = data if data else {}
@property
def source(self):
return self._source
@property
def name(self):
return self._name
def __eq__(self, other):
if isinstance(self, other.__class__):
return list(self) == list(other)
return False
def __ne__(self, other):
return not self.__eq__(other)
def __iter__(self):
return iter(self._data.items())
def __getitem__(self, which):
return self._data[which]
def lookup_destination(self, dest, primary=False):
data = self._data.get(dest, None)
if primary is False or data is None:
return data
return data[0]
def __str__(self):
return str(self._data)
class KernelRepoEntry:
def __init__(self, ks, owner, data):
if isinstance(data, list):
new_data = {'url': data[0]}
if len(data) == 1:
new_data['branch'] = 'master'
elif len(data) == 2:
new_data['branch'] = data[1]
data = new_data
self._ks = ks
self._owner = owner
self._data = data if data else {}
@property
def owner(self):
return self._owner
# XXX: should this object have a name ?
def __eq__(self, other):
if isinstance(self, other.__class__):
return self.url == other.url and self.branch == other.branch
return False
def __ne__(self, other):
return not self.__eq__(other)
@property
def url(self):
return self._data['url']
@property
def branch(self):
return self._data.get('branch', None)
def __str__(self):
return "{} {}".format(self.url, self.branch)
class KernelSnapEntry:
def __init__(self, ks, source, name, data):
self._ks = ks
self._source = source
self._name = name
self._data = data if data else {}
# Convert arches/track to publish-to form.
if 'publish-to' not in self._data:
if 'arches' in self._data:
publish_to = {}
for arch in self._data['arches']:
publish_to[arch] = [self._data.get('track', 'latest')]
self._data['publish-to'] = publish_to
# Convert stable to promote-to form.
if 'promote-to' not in self._data and 'stable' in self._data:
if self._data['stable'] is True:
self._data['promote-to'] = 'stable'
else:
self._data['promote-to'] = 'candidate'
# Assume no promote-to data to mean just to edge.
promote_to = self._data.get('promote-to', 'edge')
if isinstance(promote_to, str):
expand_promote_to = []
for risk in ('edge', 'beta', 'candidate', 'stable'):
expand_promote_to.append(risk)
if risk == promote_to:
break
self._data['promote-to'] = expand_promote_to
# Ensure we have stable when promote-to is present.
if 'promote-to' in self._data and 'stable' not in self._data:
if 'stable' in self._data['promote-to']:
self._data['stable'] = True
else:
self._data['stable'] = False
def __eq__(self, other):
if isinstance(self, other.__class__):
return self.name == other.name and self.source == other.source
return False
def __ne__(self, other):
return not self.__eq__(other)
@property
def series(self):
return self._source.series
@property
def source(self):
return self._source
@property
def name(self):
return self._name
@property
def repo(self):
data = self._data.get('repo', None)
if not data:
return None
return KernelRepoEntry(self._ks, self, data)
@property
def primary(self):
return self._data.get('primary', False)
@property
def gated(self):
return self._data.get('gated', False)
@property
def stable(self):
return self._data.get('stable', False)
@property
def qa(self):
return self._data.get('qa', False)
@property
def hw_cert(self):
return self._data.get('hw-cert', False)
@property
def arches(self):
# XXX: should this be []
return self._data.get('arches', None)
@property
def track(self):
return self._data.get('track', None)
@property
def publish_to(self):
return self._data.get('publish-to', None)
@property
def promote_to(self):
return self._data.get('promote-to', None)
def promote_to_risk(self, risk):
return risk in self._data.get('promote-to', [])
def __str__(self):
return "{} {}".format(str(self.source), self.name)
class KernelPackageEntry:
def __init__(self, ks, source, name, data):
self._ks = ks
self._source = source
self._name = name
self._data = data if data else {}
def __eq__(self, other):
if isinstance(self, other.__class__):
return self.name == other.name and self.source == other.source
return False
def __ne__(self, other):
return not self.__eq__(other)
@property
def series(self):
return self._source.series
@property
def source(self):
return self._source
@property
def name(self):
return self._name
@property
def type(self):
return self._data.get('type', None)
@property
def repo(self):
data = self._data.get('repo', None)
if not data:
return None
return KernelRepoEntry(self._ks, self, data)
def __str__(self):
return "{} {} {}".format(str(self.source), self.name, self.type)
class KernelSourceEntry:
def __init__(self, ks, series, name, data):
self._ks = ks
self._series = series
self._name = name
self._data = data if data else {}
def __eq__(self, other):
if isinstance(self, other.__class__):
return self.name == other.name and self.series == other.series
return False
def __ne__(self, other):
return not self.__eq__(other)
@property
def name(self):
return self._name
@property
def series(self):
return self._series
@property
def versions(self):
if 'versions' in self._data:
return self._data['versions']
derived_from = self.derived_from
if derived_from is not None:
return derived_from.versions
copy_forward = self.copy_forward
if copy_forward is not None:
return copy_forward.versions
# XXX: should this be []
return None
@property
def version(self):
versions = self.versions
if not versions:
return None
return versions[-1]
@property
def development(self):
return self._data.get('development', self.series.development)
@property
def supported(self):
return self._data.get('supported', self.series.supported)
@property
def severe_only(self):
return self._data.get('severe-only', False)
@property
def stakeholder(self):
return self._data.get('stakeholder', None)
@property
def packages(self):
# XXX: should this return None when empty
result = []
packages = self._data.get('packages')
if packages:
for package_key, package in packages.items():
result.append(KernelPackageEntry(self._ks, self, package_key, package))
return result
def lookup_package(self, package_key):
packages = self._data.get('packages')
if not packages or package_key not in packages:
return None
return KernelPackageEntry(self._ks, self, package_key, packages[package_key])
@property
def snaps(self):
# XXX: should this return None when empty
result = []
snaps = self._data.get('snaps')
if snaps:
for snap_key, snap in snaps.items():
result.append(KernelSnapEntry(self._ks, self, snap_key, snap))
return result
def lookup_snap(self, snap_key):
snaps = self._data.get('snaps')
if not snaps or snap_key not in snaps:
return None
return KernelSnapEntry(self._ks, self, snap_key, snaps[snap_key])
@property
def derived_from(self):
if 'derived-from' not in self._data:
return None
(series_key, source_key) = self._data['derived-from']
series = self._ks.lookup_series(series_key)
source = series.lookup_source(source_key)
return source
@property
def testable_flavours(self):
retval = []
if (self._data.get('testing') is not None and
self._data['testing'].get('flavours') is not None
):
for flavour in self._data['testing']['flavours'].keys():
fdata = self._data['testing']['flavours'][flavour]
# If we have neither arches nor clouds we represent a noop
if not fdata:
continue
arches = fdata.get('arches', None)
arches = arches if arches is not None else []
clouds = fdata.get('clouds', None)
clouds = clouds if clouds is not None else []
retval.append(KernelSourceTestingFlavourEntry(flavour, arches, clouds))
return retval
@property
def invalid_tasks(self):
retval = self._data.get('invalid-tasks', [])
if retval is None:
retval = []
return retval
@property
def copy_forward(self):
if 'copy-forward' not in self._data:
return None
# XXX: backwards compatibility.
if self._data['copy-forward'] is False:
return None
if self._data['copy-forward'] is True:
derived_from = self.derived_from
if derived_from is None:
return True
return self.derived_from
(series_key, source_key) = self._data['copy-forward']
series = self._ks.lookup_series(series_key)
source = series.lookup_source(source_key)
return source
@property
def backport(self):
return self._data.get('backport', False)
@property
def routing(self):
default = 'default'
if self.series.development:
default = 'devel'
if self.series.esm:
default = 'esm'
data = self._data.get('routing', default)
if data is None:
return data
return KernelRoutingEntry(self._ks, self, data)
@property
def swm_data(self):
return self._data.get('swm')
@property
def private(self):
return self._data.get('private', False)
def __str__(self):
return "{} {}".format(self.series.name, self.name)
class KernelSourceTestingFlavourEntry:
def __init__(self, name, arches, clouds):
self._name = name
self._arches = arches
self._clouds = clouds
@property
def name(self):
return self._name
@property
def arches(self):
return self._arches
@property
def clouds(self):
return self._clouds
class KernelSeriesEntry:
def __init__(self, ks, name, data, defaults=None):
self._ks = ks
self._name = name
self._data = {}
if defaults is not None:
self._data.update(defaults)
if data is not None:
self._data.update(data)
def __eq__(self, other):
if isinstance(self, other.__class__):
return self.name == other.name
return False
def __ne__(self, other):
return not self.__eq__(other)
@property
def name(self):
return self._name
@property
def codename(self):
return self._data.get('codename', None)
@property
def opening(self):
if 'opening' in self._data:
if self._data['opening'] is not False:
return True
return False
def opening_ready(self, *flags):
if 'opening' not in self._data:
return True
allow = self._data['opening']
if allow is None:
return False
if allow in (True, False):
return not allow
for flag in flags:
flag_allow = allow.get(flag, False)
if flag_allow is None or flag_allow is False:
return False
return True
opening_allow = opening_ready
@property
def development(self):
return self._data.get('development', False)
@property
def supported(self):
return self._data.get('supported', False)
@property
def lts(self):
return self._data.get('lts', False)
@property
def esm(self):
return self._data.get('esm', False)
def __str__(self):
return "{} ({})".format(self.name, self.codename)
@property
def sources(self):
result = []
sources = self._data.get('sources')
if sources:
for source_key, source in sources.items():
result.append(KernelSourceEntry(
self._ks, self, source_key, source))
return result
@property
def routing_table(self):
return self._data.get('routing-table', None)
def lookup_source(self, source_key):
sources = self._data.get('sources')
if not sources or source_key not in sources:
return None
return KernelSourceEntry(self._ks, self, source_key, sources[source_key])
# KernelSeries
#
class KernelSeries:
_url = 'https://git.launchpad.net/~canonical-kernel/' \
'+git/kteam-tools/plain/info/kernel-series.yaml'
_url_local = 'file://' + os.path.realpath(os.path.join(os.path.dirname(__file__),
'..', 'info', 'kernel-series.yaml'))
#_url = 'file:///home/apw/git2/kteam-tools/info/kernel-series.yaml'
#_url = 'file:///home/work/kteam-tools/info/kernel-series.yaml'
_data_txt = {}
@classmethod
def __load_once(cls, url):
if url not in cls._data_txt:
response = urlopen(url)
data = response.read()
if not isinstance(data, str):
data = data.decode('utf-8')
cls._data_txt[url] = data
return cls._data_txt[url]
def __init__(self, url=None, data=None, use_local=os.getenv("USE_LOCAL_KERNEL_SERIES_YAML", False)):
if data or url:
if url:
response = urlopen(url)
data = response.read()
if not isinstance(data, str):
data = data.decode('utf-8')
else:
data = self.__load_once(self._url_local if use_local else self._url)
self._data = yaml.safe_load(data)
self._development_series = None
self._codename_to_series = {}
for series_key, series in self._data.items():
if not series:
continue
if series.get('development', False):
self._development_series = series_key
if 'codename' in series:
self._codename_to_series[series['codename']] = series_key
# Pull out the defaults.
self._defaults_series = {}
if 'defaults' in self._data:
self._defaults_series = self._data['defaults']
del self._data['defaults']
@staticmethod
def key_series_name(series):
return [int(x) for x in series.name.split('.')]
@property
def series(self):
return [KernelSeriesEntry(self, series_key, series,
defaults=self._defaults_series)
for series_key, series in self._data.items()]
def lookup_series(self, series=None, codename=None, development=False):
if not series and not codename and not development:
raise ValueError("series/codename/development required")
if not series and codename:
if codename not in self._codename_to_series:
return None
series = self._codename_to_series[codename]
if not series and development:
if not self._development_series:
return None
series = self._development_series
if series and series not in self._data:
return None
return KernelSeriesEntry(self, series, self._data[series],
defaults=self._defaults_series)
if __name__ == '__main__':
db = KernelSeries()
series = db.lookup_series('16.04')
if series.name != '16.04':
print('series.name != 16.04')
if series.codename != 'xenial':
print('series.codename != xenial')
series2 = db.lookup_series(codename='xenial')
if series2.name != '16.04':
print('series2.name != 16.04')
if series2.codename != 'xenial':
print('series2.codename != xenial')
series3 = db.lookup_series(development=True)
if series3.name != '18.04':
print('series3.name != 18.04')
if series3.codename != 'bionic':
print('series3.codename != bionic')
print(str(series), str(series2), str(series3))
for series2 in sorted(db.series, key=db.key_series_name):
print(series2)
for source in series.sources:
print(str(source), source.series.name, source.name)
print(source.derived_from)
print(source.versions)
for package in source.packages:
print("PACKAGE", str(package))
for snap in source.snaps:
print("SNAP", str(snap), snap.arches)
# vi:set ts=4 sw=4 expandtab:

@ -27,7 +27,8 @@ class KernelWorkflowError(Exception):
def get_name_and_version_from_bug(bug): def get_name_and_version_from_bug(bug):
title_re = re.compile( title_re = re.compile(
r'^(?P<package>[a-z0-9.-]+): (?P<version>[0-9.-]+[0-9a-z.~-]*)' r'^([a-z]+\/)?(?P<package>[a-z0-9.-]+): '
+ '(?P<version>[0-9.-]+[0-9a-z.~-]*)'
+ ' -proposed tracker$') + ' -proposed tracker$')
match = title_re.search(bug.title) match = title_re.search(bug.title)
if not match: if not match:
@ -93,7 +94,8 @@ def process_sru_bug(lp, bugnum, task_callback, source_callback, context=None):
if source_name != package: if source_name != package:
print("Cannot determine base package for %s, %s vs. %s" print("Cannot determine base package for %s, %s vs. %s"
% (bugnum, source_name, package)) % (bugnum, source_name, package))
return if context['skipnamecheck']:
return
if not packages: if not packages:
print("No packages in the prepare list, don't know what to do") print("No packages in the prepare list, don't know what to do")
@ -101,7 +103,7 @@ def process_sru_bug(lp, bugnum, task_callback, source_callback, context=None):
if not '' in packages: if not '' in packages:
print("No kernel package in prepare list, only meta packages. " print("No kernel package in prepare list, only meta packages. "
"Continue review? [yN] ", end="") "Continue? [yN] ", end="")
sys.stdout.flush() sys.stdout.flush()
response = sys.stdin.readline() response = sys.stdin.readline()
if not response.strip().lower().startswith('y'): if not response.strip().lower().startswith('y'):
@ -110,7 +112,9 @@ def process_sru_bug(lp, bugnum, task_callback, source_callback, context=None):
full_packages = [] full_packages = []
for pkg in packages: for pkg in packages:
if pkg == '-lbm': if pkg == '-lbm':
pkg = '-backports-modules-3.2.0' pkg = '-backports-modules-3.2.0'
elif pkg == '-lrm':
pkg = '-restricted-modules'
real_package = re.sub(r'^linux', 'linux' + pkg, package) real_package = re.sub(r'^linux', 'linux' + pkg, package)
full_packages.append(real_package) full_packages.append(real_package)

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2012 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify

Binary file not shown.

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python3
# Manage the Launchpad build farm. # Manage the Launchpad build farm.
# #
# Copyright 2012-2014 Canonical Ltd. # Copyright 2012-2014 Canonical Ltd.
@ -83,6 +83,9 @@ parser.add_argument(
parser.add_argument( parser.add_argument(
"-d", "--disabled", action="store_const", dest="ok_filter", const=False, "-d", "--disabled", action="store_const", dest="ok_filter", const=False,
help="update only disabled builders") help="update only disabled builders")
parser.add_argument(
"--idle", action="store_const", dest="idle_filter", const=True,
help="update only idle builders")
parser.add_argument( parser.add_argument(
"--cleaning", action="store_const", dest="cleaning_filter", const=True, "--cleaning", action="store_const", dest="cleaning_filter", const=True,
help="update only builders that are stuck cleaning") help="update only builders that are stuck cleaning")
@ -173,6 +176,12 @@ def get_processor_name(processor_link):
def get_clean_status_duration(builder): def get_clean_status_duration(builder):
return datetime.now(pytz.UTC) - builder.date_clean_status_changed return datetime.now(pytz.UTC) - builder.date_clean_status_changed
def is_idle(builder):
return (
builder.builderok
and builder.current_build_link is None
and builder.clean_status == 'Clean')
def is_cleaning(builder): def is_cleaning(builder):
return ( return (
builder.builderok builder.builderok
@ -186,6 +195,8 @@ for builder in lp.builders:
continue continue
if args.ok_filter is not None and builder.builderok != args.ok_filter: if args.ok_filter is not None and builder.builderok != args.ok_filter:
continue continue
if args.idle_filter is not None and is_idle(builder) != args.idle_filter:
continue
if (args.cleaning_filter is not None if (args.cleaning_filter is not None
and is_cleaning(builder) != args.cleaning_filter): and is_cleaning(builder) != args.cleaning_filter):
continue continue

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python3
# Copyright 2013-2019 Canonical Ltd. # Copyright 2013-2019 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>
@ -22,28 +22,54 @@ from __future__ import print_function
__metaclass__ = type __metaclass__ = type
import argparse import argparse
from collections import OrderedDict
import hashlib import hashlib
import subprocess import subprocess
import sys import sys
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
from launchpadlib.launchpad import Launchpad from launchpadlib.launchpad import Launchpad
from launchpadlib.uris import web_root_for_service_root from launchpadlib.uris import web_root_for_service_root
from six.moves import shlex_quote
from six.moves.urllib.parse import (
unquote,
urlparse,
)
from ubuntutools.question import YesNoQuestion from ubuntutools.question import YesNoQuestion
import lputils import lputils
# Convenience aliases. # Convenience aliases.
image_types = { image_types = OrderedDict([
"chroot": "Chroot tarball", ("chroot", "Chroot tarball"),
"lxd": "LXD image", ("lxd", "LXD image"),
])
# Affordance for --from-livefs.
image_types_by_name = {
"livecd.ubuntu-base.rootfs.tar.gz": "Chroot tarball",
"livecd.ubuntu-base.lxd.tar.gz": "LXD image",
} }
def adjust_lp_url(parser, args, url):
parsed_url = urlparse(url)
if parsed_url.scheme != "":
root_uri = args.launchpad._root_uri
service_host = root_uri.host
web_host = urlparse(web_root_for_service_root(str(root_uri))).hostname
if parsed_url.hostname == service_host:
return url
elif parsed_url.hostname == web_host:
return parsed_url.path
else:
parser.error(
"%s is not on this Launchpad instance (%s)" % (url, web_host))
else:
return url
def describe_image_type(image_type): def describe_image_type(image_type):
if image_type == "Chroot tarball": if image_type == "Chroot tarball":
return "base chroot tarball" return "base chroot tarball"
@ -104,7 +130,87 @@ def remove_chroot(args):
return 0 return 0
def get_last_livefs_builds(livefs, architectures):
"""Get the most recent build for each of `architectures` in `livefs`."""
arch_tags = {das.self_link: das.architecture_tag for das in architectures}
builds = {}
for build in livefs.completed_builds:
arch_tag = arch_tags.get(build.distro_arch_series_link)
if arch_tag is not None and arch_tag not in builds:
builds[arch_tag] = build
if set(builds) == set(arch_tags.values()):
break
return [build for _, build in sorted(builds.items())]
def set_chroots_from_livefs(args):
"""Set a whole batch of base images at once, for convenience."""
if args.image_type is None:
image_types = [args.image_type]
else:
image_types = list(image_types.values())
livefs = args.launchpad.load(args.livefs_url)
builds = get_last_livefs_builds(livefs, args.architectures)
todo = []
for build in builds:
das = build.distro_arch_series
suite_arch = "%s/%s" % (args.suite, das.architecture_tag)
for image_url in build.getFileUrls():
image_name = unquote(urlparse(image_url).path).split('/')[-1]
image_type = image_types_by_name.get(image_name)
if image_type is not None:
previous_url = das.getChrootURL(
pocket=args.pocket, image_type=image_type)
if previous_url is not None:
print("Previous %s for %s: %s" % (
describe_image_type(image_type), suite_arch,
previous_url))
print("New %s for %s: %s" % (
describe_image_type(image_type), suite_arch, image_url))
todo.append(
(das, build.self_link, image_name, args.pocket,
image_type))
if todo:
if args.dry_run:
print("Not setting base images in dry-run mode.")
else:
if not args.confirm_all:
if YesNoQuestion().ask("Set these base images", "no") == "no":
return 0
for das, build_url, image_name, pocket, image_type in todo:
das.setChrootFromBuild(
livefsbuild=build_url, filename=image_name, pocket=pocket,
image_type=image_type)
print()
print(
"The following commands will roll back to these images if a "
"future set is broken:")
base_command = [
"manage-chroot",
"-l", args.launchpad_instance,
"-d", args.distribution.name,
"-s", args.suite,
]
for das, build_url, image_name, _, image_type in todo:
command = base_command + [
"-a", das.architecture_tag,
"-i", image_type,
"--from-build", build_url,
"-f", image_name,
"set",
]
print(" ".join(shlex_quote(arg) for arg in command))
return 0
def set_chroot(args): def set_chroot(args):
if args.livefs_url is not None:
return set_chroots_from_livefs(args)
das = args.architectures[0] das = args.architectures[0]
previous_url = das.getChrootURL( previous_url = das.getChrootURL(
pocket=args.pocket, image_type=args.image_type) pocket=args.pocket, image_type=args.image_type)
@ -128,7 +234,7 @@ def set_chroot(args):
return 0 return 0
if args.build_url: if args.build_url:
das.setChrootFromBuild( das.setChrootFromBuild(
livefsbuild=urlparse(args.build_url).path, livefsbuild=args.build_url,
filename=args.filepath, filename=args.filepath,
pocket=args.pocket, image_type=args.image_type) pocket=args.pocket, image_type=args.image_type)
else: else:
@ -165,11 +271,16 @@ def main():
"-s", "--suite", "--series", dest="suite", metavar="SUITE", "-s", "--suite", "--series", dest="suite", metavar="SUITE",
help="manage base images for SUITE") help="manage base images for SUITE")
parser.add_argument( parser.add_argument(
"-a", "--architecture", metavar="ARCHITECTURE", required=True, "-a", "--architecture", metavar="ARCHITECTURE",
help="manage base images for ARCHITECTURE") help="manage base images for ARCHITECTURE")
parser.add_argument( parser.add_argument(
"-i", "--image-type", metavar="TYPE", default="Chroot tarball", "-i", "--image-type", metavar="TYPE",
help="manage base images of type TYPE") help="manage base images of type TYPE")
parser.add_argument(
"--from-livefs", dest="livefs_url", metavar="URL",
help=(
"Live filesystem to set base images from (sets base images for "
"all available architectures and image types)"))
parser.add_argument( parser.add_argument(
"--from-build", dest="build_url", metavar="URL", "--from-build", dest="build_url", metavar="URL",
help="Live filesystem build URL to set base image from") help="Live filesystem build URL to set base image from")
@ -179,15 +290,23 @@ def main():
parser.add_argument("command", choices=sorted(commands.keys())) parser.add_argument("command", choices=sorted(commands.keys()))
args = parser.parse_args() args = parser.parse_args()
if args.command == "set" and args.filepath is None: if args.command == "set" and args.livefs_url is None:
parser.error("The set command requires a base image file path (-f).") if args.architecture is None:
parser.error("The set command requires an architecture (-a).")
if args.image_type not in image_types.values(): if args.filepath is None:
image_type = image_types.get(args.image_type.lower()) parser.error(
if image_type is not None: "The set command requires a base image file path (-f).")
args.image_type = image_type
else: if args.command != "set" or args.livefs_url is None:
parser.error("Unknown image type '%s'." % args.image_type) if args.image_type is None:
args.image_type = "Chroot tarball"
if args.image_type not in image_types.values():
image_type = image_types.get(args.image_type.lower())
if image_type is not None:
args.image_type = image_type
else:
parser.error("Unknown image type '%s'." % args.image_type)
if args.command in ("get", "info"): if args.command in ("get", "info"):
login_method = Launchpad.login_anonymously login_method = Launchpad.login_anonymously
@ -197,16 +316,11 @@ def main():
"manage-chroot", args.launchpad_instance, version="devel") "manage-chroot", args.launchpad_instance, version="devel")
lputils.setup_location(args) lputils.setup_location(args)
if args.command == "set" and args.build_url: if args.command == "set":
parsed_build_url = urlparse(args.build_url) if args.livefs_url is not None:
if parsed_build_url.scheme != "": args.livefs_url = adjust_lp_url(parser, args, args.livefs_url)
service_host = args.launchpad._root_uri.host if args.build_url is not None:
web_host = urlparse(web_root_for_service_root( args.build_url = adjust_lp_url(parser, args, args.build_url)
str(args.launchpad._root_uri))).hostname
if parsed_build_url.hostname not in (service_host, web_host):
parser.error(
"%s is not on this Launchpad instance (%s)" % (
args.build_url, web_host))
return commands[args.command](args) return commands[args.command](args)

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python3
# Copyright (C) 2017 Canonical Ltd. # Copyright (C) 2017 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Copyright (C) 2011, 2012 Canonical Ltd. # Copyright (C) 2011, 2012 Canonical Ltd.
# Author: Martin Pitt <martin.pitt@ubuntu.com> # Author: Martin Pitt <martin.pitt@ubuntu.com>

@ -1,7 +1,8 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Copyright (C) 2011, 2012 Canonical Ltd. # Copyright (C) 2011, 2012 Canonical Ltd.
# Author: Martin Pitt <martin.pitt@ubuntu.com> # Author: Martin Pitt <martin.pitt@ubuntu.com>
# Author: Brian Murray <brian@ubuntu.com>
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -28,6 +29,10 @@ import sys
import time import time
from charts import make_chart, make_chart_header from charts import make_chart, make_chart_header
from utils import read_tag_file
default_base = '/home/ubuntu-archive/mirror/ubuntu'
rdeps_with_alternates = []
def parse_checkrdepends_file(path, pkgmap): def parse_checkrdepends_file(path, pkgmap):
@ -48,24 +53,70 @@ def parse_checkrdepends_file(path, pkgmap):
pkgmap.setdefault(rdep, (cur_component, []))[1].append(cur_arch) pkgmap.setdefault(rdep, (cur_component, []))[1].append(cur_arch)
def _pkg_removable(pkg, nbs, checked_v): def _pkg_removable(options, pkg, nbs, checked_v):
'''Recursively check if pakcage is removable. '''Recursively check if package is removable.
checked_v is the working set of already checked vertices, to avoid infinite checked_v is the working set of already checked vertices, to avoid infinite
loops. loops.
''' '''
checked_v.add(pkg) checked_v.add(pkg)
packages = {}
for rdep in nbs.get(pkg, []): for rdep in nbs.get(pkg, []):
if rdep in checked_v: if rdep in checked_v:
continue continue
#checked_v.add(rdep) global rdeps_with_alternates
if not rdep in nbs: # utilize a copy of the arches as nbs will be modified
arches = list(nbs[pkg][rdep][1])
for arch in arches:
alternate_available = False
if arch == 'build':
ptype = 'source'
file = 'Sources'
else:
ptype = 'binary-%s' % arch
file = 'Packages'
key = '%s/dists/%s/%s/%s/%s.gz' % \
(options.archive_base, options.suite, nbs[pkg][rdep][0],
ptype, file)
if key not in packages:
packages[key] = read_tag_file(key, rdep)
stanzas = packages[key]
for stanza in stanzas:
if 'binary' in ptype:
fields = ('Pre-Depends', 'Depends', 'Recommends')
else:
fields = ('Build-Depends', 'Build-Depends-Indep')
for field in fields:
if field not in stanza:
continue
if '|' not in stanza[field]:
continue
for or_dep in stanza[field].split(','):
if '|' not in or_dep:
continue
alternatives = [dep.strip()
for dep in or_dep.split('|')]
if pkg not in alternatives:
continue
for dep in alternatives:
if dep == pkg:
continue
if dep not in nbs:
alternate_available = True
break
if alternate_available:
nbs[pkg][rdep][1].remove(arch)
if len(nbs[pkg][rdep][1]) == 0:
rdeps_with_alternates.append(rdep)
if rdep not in nbs and rdep not in rdeps_with_alternates:
try: try:
checked_v.remove(rdep) checked_v.remove(rdep)
except KeyError: except KeyError:
pass pass
return False return False
if not _pkg_removable(rdep, nbs, checked_v): if not _pkg_removable(options, rdep, nbs, checked_v):
try: try:
checked_v.remove(rdep) checked_v.remove(rdep)
except KeyError: except KeyError:
@ -74,7 +125,7 @@ def _pkg_removable(pkg, nbs, checked_v):
return True return True
def get_removables(nbs): def get_removables(options, nbs):
'''Get set of removable packages. '''Get set of removable packages.
This includes packages with no rdepends and disconnected subgraphs, i. e. This includes packages with no rdepends and disconnected subgraphs, i. e.
@ -86,10 +137,9 @@ def get_removables(nbs):
if p in removable: if p in removable:
continue continue
checked_v = set() checked_v = set()
if _pkg_removable(p, nbs, checked_v): if _pkg_removable(options, p, nbs, checked_v):
# we can add the entire cluster here, not just p; avoids # we only add packages which are nbs to removable
# re-checking the other vertices in that cluster removable.update([p for p in checked_v if p in nbs])
removable.update(checked_v)
return removable return removable
@ -97,6 +147,7 @@ def get_removables(nbs):
def html_report(options, nbs, removables): def html_report(options, nbs, removables):
'''Generate HTML report from NBS map.''' '''Generate HTML report from NBS map.'''
global rdeps_with_alternates
print('''\ print('''\
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@ -148,9 +199,11 @@ def html_report(options, nbs, removables):
cls = 'removable' cls = 'removable'
else: else:
cls = 'normal' cls = 'normal'
print('<tr><th colspan="4"><span class="%s">%s</span></td></tr>' % print('<tr><th colspan="4"><span class="%s">%s</span></th></tr>\n' %
(cls, pkg), end="") (cls, pkg), end="")
for rdep in sorted(nbsmap): for rdep in sorted(nbsmap):
if rdep in rdeps_with_alternates:
continue
(component, arches) = nbsmap[rdep] (component, arches) = nbsmap[rdep]
if component in ('main', 'restricted'): if component in ('main', 'restricted'):
@ -208,8 +261,12 @@ def main():
parser = OptionParser( parser = OptionParser(
usage="%prog <checkrdepends output directory>", usage="%prog <checkrdepends output directory>",
description="Generate an HTML report of current NBS binary packages.") description="Generate an HTML report of current NBS binary packages.")
parser.add_option('-B', '--archive-base', dest='archive_base',
help=('archive base directory (default: %s)' %
default_base),
default=default_base)
parser.add_option('-d', '--distribution', default='ubuntu') parser.add_option('-d', '--distribution', default='ubuntu')
parser.add_option('-s', '--suite', default='disco') parser.add_option('-s', '--suite', default='groovy')
parser.add_option( parser.add_option(
'--csv-file', help='record CSV time series data in this file') '--csv-file', help='record CSV time series data in this file')
options, args = parser.parse_args() options, args = parser.parse_args()
@ -246,7 +303,7 @@ def main():
# rd.replace('-', '').replace('.', '')), file=dot) # rd.replace('-', '').replace('.', '')), file=dot)
# print('}', file=dot) # print('}', file=dot)
removables = get_removables(nbs) removables = get_removables(options, nbs)
html_report(options, nbs, removables) html_report(options, nbs, removables)

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python3
# Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd.
# Authors: # Authors:
@ -32,15 +32,10 @@ import shutil
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
try:
from urllib.parse import unquote, urlsplit
from urllib.request import urlopen, urlretrieve
except ImportError:
from urllib import unquote, urlretrieve
from urllib2 import urlopen
from urlparse import urlsplit
from launchpadlib.launchpad import Launchpad from launchpadlib.launchpad import Launchpad
from six.moves.urllib.parse import unquote, urlsplit
from six.moves.urllib.request import urlopen, urlretrieve
from ubuntutools.question import YesNoQuestion from ubuntutools.question import YesNoQuestion
import lputils import lputils
@ -81,7 +76,7 @@ def find_matching_uploads(options, explicit_suite):
continue continue
changes_file = urlopen(upload.changes_file_url) changes_file = urlopen(upload.changes_file_url)
try: try:
changes = changes_file.read() changes = changes_file.read().decode("UTF-8", errors="replace")
finally: finally:
changes_file.close() changes_file.close()
if (" unstable; urgency=" not in changes and if (" unstable; urgency=" not in changes and

@ -0,0 +1,78 @@
#!/bin/bash
# Copyright (C) 2020 Canonical Ltd.
# Author: Iain Lane <iain.lane@canonical.com>
# 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; either version 2 of the License, or
# (at your option) any later version.
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Compare a given source package with the oem-qemu-meta reference package, to
# see if it complies with the MIR exception granted in
# https://wiki.ubuntu.com/MIRTeam/Exceptions/OEM
set -e
set -u
shopt -s nullglob
THIS="$(basename "${0}")"
ensure_programs() {
if [ ${#} -gt 0 ] && ! type "${1}" >/dev/null 2>/dev/null; then
echo "Required program $1 not found." >&2
exit 1
fi
shift
if [ ${#} -gt 0 ]; then
ensure_programs "${@}"
fi
}
if [ ${#} -ne 1 ]; then
echo -e "Usage: ${THIS} <dsc>\\n" >&2
cat <<EOM >&2
Compare the given package against the oem-qemu-meta reference package. Check
that all the differences are inconsequential or expected (different modalias,
different package name), and then promote or NEW the package directly to main.
https://wiki.ubuntu.com/MIRTeam/Exceptions/OEM
EOM
exit 1
fi
ensure_programs pull-lp-source debdiff
if ! [ -e "${1}" ]; then
echo "${THIS}: ${1} not found" >&2
exit 1
fi
DSC="$(realpath -e "${1}")"
WORKINGDIR=$(mktemp -d)
trap 'rm -rf ${WORKINGDIR}' EXIT HUP INT QUIT TERM
pushd "${WORKINGDIR}" >/dev/null
# Download the reference package
pull-lp-source oem-qemu-meta -d 2>/dev/null
if [ -t 1 ] && type colordiff >/dev/null 2>/dev/null; then
debdiff oem-qemu-meta_*.dsc "${DSC}" 2>/dev/null | colordiff
else
debdiff oem-qemu-meta_*.dsc "${DSC}" 2>/dev/null
fi

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python2.7
from __future__ import print_function from __future__ import print_function

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python2.7
from __future__ import print_function from __future__ import print_function

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2013 Canonical Ltd. # Copyright (C) 2013 Canonical Ltd.

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2013 Canonical Ltd. # Copyright (C) 2013 Canonical Ltd.

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Copyright (C) 2013 Canonical Ltd. # Copyright (C) 2013 Canonical Ltd.
# Author: Brian Murray <brian.murray@canonical.com> # Author: Brian Murray <brian.murray@canonical.com>
@ -261,6 +261,8 @@ def create_email_notifications(releases, spph_buckets):
signer_name = signer.name signer_name = signer.name
# use the changes file as a backup method for determining email addresses # use the changes file as a backup method for determining email addresses
changes_file_url = spph.changesFileUrl() changes_file_url = spph.changesFileUrl()
changer_name = ''
changer_email = ''
try: try:
changes_file = urlopen(changes_file_url) changes_file = urlopen(changes_file_url)
for line in changes_file.readlines(): for line in changes_file.readlines():

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python2.7
# Check for override mismatches between pockets # Check for override mismatches between pockets
# Copyright (C) 2005, 2008, 2011, 2012 Canonical Ltd. # Copyright (C) 2005, 2008, 2011, 2012 Canonical Ltd.

@ -1,4 +1,4 @@
#! /usr/bin/env python #! /usr/bin/env python2.7
from __future__ import print_function from __future__ import print_function

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python2.7
# Copyright (C) 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2010, 2011, 2012 Canonical Ltd.

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Copyright (C) 2011 Canonical Ltd. # Copyright (C) 2011 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python2.7
# Synchronise package priorities with germinate output # Synchronise package priorities with germinate output
# Copyright (C) 2005, 2009, 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2005, 2009, 2010, 2011, 2012 Canonical Ltd.

@ -99,8 +99,12 @@ def parse_removals_file(options, removals_file):
# Removed the following packages from unstable: # Removed the following packages from unstable:
elif state == "removed from": elif state == "removed from":
# complete processing of the date from the preceding line # complete processing of the date from the preceding line
removal["date"] = time.mktime(time.strptime( try:
date, "%a, %d %b %Y %H:%M:%S %z")) removal["date"] = time.mktime(time.strptime(
date, "%a, %d %b %Y %H:%M:%S %z"))
except ValueError:
removal["date"] = time.mktime(time.strptime(
date, "%a %d %b %H:%M:%S %Z %Y"))
removal["ftpmaster"] = ftpmaster.replace("] [ftpmaster: ", "") removal["ftpmaster"] = ftpmaster.replace("] [ftpmaster: ", "")
prefix = "Removed the following packages from " prefix = "Removed the following packages from "
@ -173,7 +177,7 @@ def show_reverse_depends(options, package):
series_name = options.series.name series_name = options.series.name
commands = ( commands = (
["reverse-depends", "-r", series_name, "src:%s" % package], ["reverse-depends", "-r", series_name, "src:%s" % package],
["reverse-depends", "-r", series_name, "-b", "src:%s" % package], ["reverse-depends", "-r", series_name, "-a", "source", "src:%s" % package],
) )
for command in commands: for command in commands:
subp = subprocess.Popen(command, stdout=subprocess.PIPE) subp = subprocess.Popen(command, stdout=subprocess.PIPE)

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python2.7
# Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2012 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>
@ -34,16 +34,18 @@ def promote(options, name, version, architecture):
else: else:
display = "%s/%s/%s" % (name, version, architecture) display = "%s/%s/%s" % (name, version, architecture)
# check to see if the package and version has already been published or is
# pending publication
if architecture is None: if architecture is None:
try: try:
release_sources = options.archive.getPublishedSources( release_sources = options.archive.getPublishedSources(
source_name=name, version=version, source_name=name, version=version,
distro_series=options.series, pocket="Release", distro_series=options.series, pocket=options.pocket.title(),
exact_match=True, status="Published") exact_match=True, status="Published")
except IndexError: except IndexError:
release_sources = options.archive.getPublishedSources( release_sources = options.archive.getPublishedSources(
source_name=name, version=version, source_name=name, version=version,
distro_series=options.series, pocket="Release", distro_series=options.series, pocket=options.pocket.title(),
exact_match=True, status="Pending") exact_match=True, status="Pending")
except HTTPError as e: except HTTPError as e:
print("getPublishedSources %s: %s" % (display, e.content), print("getPublishedSources %s: %s" % (display, e.content),
@ -60,12 +62,16 @@ def promote(options, name, version, architecture):
sys.stdout.flush() sys.stdout.flush()
try: try:
percentage = None
if options.pocket == 'updates':
percentage = 10
options.archive.copyPackage( options.archive.copyPackage(
source_name=name, version=version, source_name=name, version=version,
from_archive=options.archive, from_archive=options.archive,
from_series=options.series.name, from_pocket="Proposed", from_series=options.series.name, from_pocket="Proposed",
to_series=options.series.name, to_pocket="Release", to_series=options.series.name, to_pocket=options.pocket.title(),
include_binaries=True, sponsored=options.requestor, include_binaries=True, sponsored=options.requestor,
phased_update_percentage=percentage,
auto_approve=True) auto_approve=True)
except HTTPError as e: except HTTPError as e:
print("copyPackage %s: %s" % (display, e.content), file=sys.stderr) print("copyPackage %s: %s" % (display, e.content), file=sys.stderr)
@ -87,7 +93,8 @@ def promote(options, name, version, architecture):
if architecture is None: if architecture is None:
try: try:
proposed_source.requestDeletion(removal_comment="moved to release") proposed_source.requestDeletion(removal_comment="moved to %s" %
options.pocket)
except HTTPError as e: except HTTPError as e:
print("requestDeletion %s: %s" % (display, e.content), print("requestDeletion %s: %s" % (display, e.content),
file=sys.stderr) file=sys.stderr)
@ -102,7 +109,8 @@ def promote(options, name, version, architecture):
if architecture != "i386": if architecture != "i386":
continue continue
try: try:
bpph.requestDeletion(removal_comment="moved to release") bpph.requestDeletion(removal_comment="moved to %s" %
options.pocket)
except HTTPError as e: except HTTPError as e:
print("requestDeletion %s/%s/%s: %s" % print("requestDeletion %s/%s/%s: %s" %
(bpph.binary_package_name, bpph.binary_package_version, (bpph.binary_package_name, bpph.binary_package_version,
@ -173,9 +181,15 @@ def main():
parser.add_option( parser.add_option(
"-s", "--series", dest="suite", "-s", "--series", dest="suite",
metavar="SERIES", help="promote from SERIES-proposed to SERIES") metavar="SERIES", help="promote from SERIES-proposed to SERIES")
parser.add_option(
"-p", "--pocket", default="release",
help="the pocket to which the package should be copied")
options, args = parser.parse_args() options, args = parser.parse_args()
if len(args) != 1: if len(args) != 1:
parser.error("need britney output delta file") parser.error("need britney output delta file")
if options.pocket not in ('updates', 'release'):
parser.error("pocket must be updates or release not %s" %
options.pocket)
options.launchpad = Launchpad.login_with( options.launchpad = Launchpad.login_with(
"promote-to-release", options.launchpad_instance, version="devel") "promote-to-release", options.launchpad_instance, version="devel")

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2010, 2011, 2012 Canonical Ltd.
@ -54,8 +54,8 @@ product_re = re.compile(
'(alternate|desktop|dvd|server(?: subiquity)?|mobile|base|active|wubi)(?: preinstalled)? ' '(alternate|desktop|dvd|server(?: subiquity)?|mobile|base|active|wubi)(?: preinstalled)? '
'(i386|amd64$|amd64\+mac|armel$|armel\+dove|armel\+omap$|armel\+omap4|' '(i386|amd64$|amd64\+mac|armel$|armel\+dove|armel\+omap$|armel\+omap4|'
'armel\+ac100|armel\+mx5|armhf$|armhf\+omap$|armhf\+omap4|armhf\+ac100|' 'armel\+ac100|armel\+mx5|armhf$|armhf\+omap$|armhf\+omap4|armhf\+ac100|'
'armhf\+mx5|armhf\+nexus7|armhf\+raspi2|armhf\+raspi3|arm64$|arm64\+raspi3|' 'armhf\+mx5|armhf\+nexus7|armhf\+raspi$|armhf\+raspi2|armhf\+raspi3|'
'powerpc|ppc64el|s390x)', re.I) 'arm64$|arm64\+raspi$|arm64\+raspi3|powerpc|ppc64el|s390x)', re.I)
# map an image type from the ISO tracker to a source directory for # map an image type from the ISO tracker to a source directory for
# publish-release # publish-release
@ -67,6 +67,7 @@ type_map = {
'mobile': 'daily-live', 'mobile': 'daily-live',
'active': 'daily-live', 'active': 'daily-live',
'server': 'daily', 'server': 'daily',
'legacy-server': 'daily',
'base': 'daily', 'base': 'daily',
'wubi': 'wubi', 'wubi': 'wubi',
'preinstalled-desktop': 'daily-preinstalled', 'preinstalled-desktop': 'daily-preinstalled',
@ -171,11 +172,19 @@ def parse_iso_tracker(opts):
type = 'preinstalled-%s' % type type = 'preinstalled-%s' % type
if 'Server arm64+raspi3' in product: if 'Server arm64+raspi3' in product:
type = 'preinstalled-%s' % type type = 'preinstalled-%s' % type
if 'Server armhf+raspi' in product:
type = 'preinstalled-%s' % type
if 'Server arm64+raspi' in product:
type = 'preinstalled-%s' % type
if 'Server Subiquity' in product: if 'Server Subiquity' in product:
type = 'live-server' type = 'live-server'
project = 'ubuntu' project = 'ubuntu'
if 'Preinstalled' in product: if 'Preinstalled' in product:
type = 'preinstalled-%s' % type type = 'preinstalled-%s' % type
if (ms.series_string == u'Focal' and
project == 'ubuntu' and type == 'server'):
project = 'ubuntu-server'
type = 'legacy-server'
if project == 'kubuntu' and type == 'mobile': if project == 'kubuntu' and type == 'mobile':
project = 'kubuntu-mobile' project = 'kubuntu-mobile'
if project == 'kubuntu' and type == 'active': if project == 'kubuntu' and type == 'active':

@ -77,7 +77,11 @@ class QATrackerRPCObject():
except ValueError: except ValueError:
setattr(self, key, None) setattr(self, key, None)
else: else:
setattr(self, key, str(rpc_dict[key])) import sys
if sys.version_info > (3,):
setattr(self, key, str(rpc_dict[key]))
else:
setattr(self, key, unicode(rpc_dict[key]))
self.tracker = tracker self.tracker = tracker

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python3
# Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2012 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>
@ -80,12 +80,12 @@ def queue_items(options, args):
if not args: if not args:
args = [''] args = ['']
items = collections.OrderedDict() items = []
for arg in args: for arg in args:
arg = arg.strip() arg = arg.strip()
if arg.isdigit(): if arg.isdigit():
item = queue_item(options, arg) item = queue_item(options, arg)
if item in items: if item.self_link in {item.self_link for item in items}:
continue continue
if item.status != options.queue: if item.status != options.queue:
raise ValueError( raise ValueError(
@ -103,7 +103,7 @@ def queue_items(options, args):
item_suite, options.distribution.name, item_suite, options.distribution.name,
options.suite)) options.suite))
if queue_item_allowed(options, item): if queue_item_allowed(options, item):
items[item] = 1 items.append(item)
else: else:
kwargs = {} kwargs = {}
if "/" in arg: if "/" in arg:
@ -116,7 +116,7 @@ def queue_items(options, args):
**kwargs) **kwargs)
for item in new_items: for item in new_items:
if queue_item_allowed(options, item): if queue_item_allowed(options, item):
items[item] = 1 items.append(item)
return items return items

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd.
# Copyright (C) 2010 Scott Kitterman <scott@kitterman.com> # Copyright (C) 2010 Scott Kitterman <scott@kitterman.com>
@ -41,7 +41,7 @@ import webbrowser
from launchpadlib.launchpad import Launchpad from launchpadlib.launchpad import Launchpad
default_release = 'cosmic' default_release = 'focal'
lp = None lp = None

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Copyright (C) 2012 Canonical, Ltd. # Copyright (C) 2012 Canonical, Ltd.
# Author: Brian Murray <brian@canonical.com> # Author: Brian Murray <brian@canonical.com>

@ -1,4 +1,4 @@
#! /usr/bin/python #! /usr/bin/python3
# Copyright 2012 Canonical Ltd. # Copyright 2012 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com> # Author: Colin Watson <cjwatson@ubuntu.com>

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Rescore all builds in a PPA. # Rescore all builds in a PPA.
# #
# Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2012 Canonical Ltd.

@ -31,7 +31,7 @@ import yaml
import json import json
request_url = 'https://autopkgtest.ubuntu.com/request.cgi' request_url = 'https://autopkgtest.ubuntu.com/request.cgi'
default_series = 'disco' default_series = 'groovy'
args = None args = None
@ -65,10 +65,10 @@ def parse_args():
parser.add_argument('--state', default='REGRESSION', parser.add_argument('--state', default='REGRESSION',
help='generate commands for given test state (default: %(default)s)') help='generate commands for given test state (default: %(default)s)')
parser.add_argument('--max-age', type=float, metavar='DAYS', parser.add_argument('--max-age', type=float, metavar='DAYS',
help='only consider candiates which are at most ' help='only consider candidates which are at most '
'this number of days old (float allowed)') 'this number of days old (float allowed)')
parser.add_argument('--min-age', type=float, metavar='DAYS', parser.add_argument('--min-age', type=float, metavar='DAYS',
help='only consider candiates which are at least ' help='only consider candidates which are at least '
'this number of days old (float allowed)') 'this number of days old (float allowed)')
parser.add_argument('--blocks', parser.add_argument('--blocks',
help='rerun only those tests that were triggered ' help='rerun only those tests that were triggered '

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Copyright (C) 2008, 2009, 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2008, 2009, 2010, 2011, 2012 Canonical Ltd.

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python3
# Copyright (C) 2011, 2012 Canonical Ltd. # Copyright (C) 2011, 2012 Canonical Ltd.
# Author: Martin Pitt <martin.pitt@canonical.com> # Author: Martin Pitt <martin.pitt@canonical.com>
@ -28,27 +28,32 @@ from __future__ import print_function
from collections import defaultdict from collections import defaultdict
from functools import partial from functools import partial
import datetime
import optparse import optparse
import os
import subprocess
import sys import sys
import time
import unittest import unittest
try: from six.moves.urllib.request import urlopen
from urllib.request import urlopen from io import TextIOWrapper
except ImportError:
from urllib import urlopen
from launchpadlib.launchpad import Launchpad from launchpadlib.launchpad import Launchpad
from kernel_series import KernelSeries
# Each entry in this list is a list of source packages that are known # Each entry in this list is a list of source packages that are known
# to have inter-dependencies and must be released simultaneously. # to have inter-dependencies and must be released simultaneously.
# If possible, each list should be ordered such that earlier # If possible, each list should be ordered such that earlier
# entries could be released slightly before subsequent entries. # entries could be released slightly before subsequent entries.
RELEASE_TOGETHER_PACKAGE_GROUPS = [ RELEASE_TOGETHER_PACKAGE_GROUPS = [
['linux-hwe', 'linux-meta-hwe'], ['linux-hwe', 'linux-signed-hwe', 'linux-meta-hwe'],
['linux', 'linux-meta'], ['linux', 'linux-signed', 'linux-meta'],
['grub2', 'grub2-signed'], ['grub2', 'grub2-signed'],
['shim', 'shim-signed'], ['shim', 'shim-signed'],
['libreoffice', 'libreoffice-l10n'],
] ]
MISSING_PACKAGES_FROM_GROUP = ( MISSING_PACKAGES_FROM_GROUP = (
@ -59,6 +64,8 @@ MISSING_PACKAGES_FROM_GROUP = (
" https://lists.ubuntu.com/archives/ubuntu-devel/2018-June/040380.html\n\n" " https://lists.ubuntu.com/archives/ubuntu-devel/2018-June/040380.html\n\n"
"To ignore this message, pass '--skip-package-group-check'.") "To ignore this message, pass '--skip-package-group-check'.")
BZR_HINT_BRANCH = "lp:~ubuntu-sru/britney/hints-ubuntu-%s"
def check_package_sets(packages): def check_package_sets(packages):
"""Return a re-ordered list of packages respecting the PACKAGE_SETS """Return a re-ordered list of packages respecting the PACKAGE_SETS
@ -97,8 +104,8 @@ def check_package_sets(packages):
class CheckPackageSets(unittest.TestCase): class CheckPackageSets(unittest.TestCase):
def test_expected_linux_order_fixed(self): def test_expected_linux_order_fixed(self):
self.assertEqual( self.assertEqual(
['pkg1', 'linux', 'linux-meta', 'pkg2'], ['pkg1', 'linux', 'linux-signed', 'linux-meta', 'pkg2'],
check_package_sets(['pkg1', 'linux-meta', 'linux', 'pkg2'])) check_package_sets(['pkg1', 'linux-meta', 'linux', 'linux-signed', 'pkg2']))
def test_raises_value_error_on_missing(self): def test_raises_value_error_on_missing(self):
self.assertRaises( self.assertRaises(
@ -127,7 +134,7 @@ def match_srubugs(options, changesfileurl):
return bugs return bugs
# Load changesfile # Load changesfile
changelog = urlopen(changesfileurl) changelog = TextIOWrapper(urlopen(changesfileurl), encoding='utf-8')
bugnums = [] bugnums = []
for l in changelog: for l in changelog:
if l.startswith('Launchpad-Bugs-Fixed: '): if l.startswith('Launchpad-Bugs-Fixed: '):
@ -155,7 +162,7 @@ def update_sru_bug(bug, pkg):
sru_team = launchpad.people['ubuntu-sru'] sru_team = launchpad.people['ubuntu-sru']
bug.unsubscribe(person=sru_team) bug.unsubscribe(person=sru_team)
text = ("The verification of the Stable Release Update for %s has " text = ("The verification of the Stable Release Update for %s has "
"completed successfully and the package has now been released " "completed successfully and the package is now being released "
"to -updates. Subsequently, the Ubuntu Stable Release Updates " "to -updates. Subsequently, the Ubuntu Stable Release Updates "
"Team is being unsubscribed and will not receive messages " "Team is being unsubscribed and will not receive messages "
"about this bug report. In the event that you encounter " "about this bug report. In the event that you encounter "
@ -178,10 +185,10 @@ def get_versions(options, sourcename):
'published': proposed_date} 'published': proposed_date}
''' '''
versions = defaultdict(dict) versions = defaultdict(dict)
if options.esm: if src_archive.reference == 'ubuntu':
pocket = 'Release'
else:
pocket = 'Proposed' pocket = 'Proposed'
else:
pocket = 'Release'
matches = src_archive.getPublishedSources( matches = src_archive.getPublishedSources(
source_name=sourcename, exact_match=not options.pattern, source_name=sourcename, exact_match=not options.pattern,
@ -192,9 +199,9 @@ def get_versions(options, sourcename):
source_name=match.source_package_name, exact_match=True, source_name=match.source_package_name, exact_match=True,
status='Published', distro_series=series): status='Published', distro_series=series):
key = pub.pocket.lower() key = pub.pocket.lower()
# special case for ESM ppas, which don't have pockets but need # special case for ppas, which don't have pockets but need
# to be treated as -proposed # to be treated as -proposed
if options.esm and key == 'release': if pocket == 'Release' and key == 'release':
key = 'proposed' key = 'proposed'
versions[pub.source_package_name][key] = ( versions[pub.source_package_name][key] = (
pub.source_package_version) pub.source_package_version)
@ -212,7 +219,6 @@ def get_versions(options, sourcename):
if pocket in pub.pocket: if pocket in pub.pocket:
versions[pub.source_package_name]['changesfile'] = ( versions[pub.source_package_name]['changesfile'] = (
pub.changesFileUrl()) pub.changesFileUrl())
# devel version # devel version
if devel_series: if devel_series:
for pub in src_archive.getPublishedSources( for pub in src_archive.getPublishedSources(
@ -227,81 +233,142 @@ def get_versions(options, sourcename):
return versions return versions
def release_package(options, package): def release_packages(options, packages):
'''Release a package.''' '''Release the packages listed in the packages argument.'''
pkg_versions_map = get_versions(options, package) pkg_versions_map = {}
if not pkg_versions_map: # Dictionary of packages and their versions that need copying by britney.
message = 'ERROR: No such package, ' + package + ', in -proposed, aborting\n' # Those packages have unblock hints added.
sys.stderr.write(message) packages_to_britney = {}
sys.exit(1)
for pkg, versions in pkg_versions_map.iteritems(): for package in packages:
print('--- Releasing %s ---' % pkg) pkg_versions_map[package] = get_versions(options, package)
print('Proposed: %s' % versions['proposed']) if not pkg_versions_map[package]:
if 'security' in versions: message = ('ERROR: No such package, ' + package + ', in '
print('Security: %s' % versions['security']) '-proposed, aborting\n')
if 'updates' in versions: sys.stderr.write(message)
print('Updates: %s' % versions['updates']) sys.exit(1)
else:
print('Release: %s' % versions.get('release'))
if options.devel and 'devel' in versions:
print('Devel: %s' % versions['devel'])
copy = partial(
dst_archive.copyPackage, from_archive=src_archive,
include_binaries=True, source_name=pkg,
version=versions['proposed'], auto_approve=True)
if options.devel:
if ('devel' not in versions or
versions['devel'] in (
versions.get('updates', 'notexisting'),
versions['release'])):
if not options.no_act:
copy(to_pocket='Proposed', to_series=devel_series.name)
print('Version in %s matches development series, '
'copied to %s-proposed' % (release, devel_series.name))
else:
print('ERROR: Version in %s does not match development '
'series, not copying' % release)
if options.no_act: for pkg, versions in pkg_versions_map[package].items():
if options.release: print('--- Releasing %s ---' % pkg)
print('Would copy to %s' % release) print('Proposed: %s' % versions['proposed'])
if 'security' in versions:
print('Security: %s' % versions['security'])
if 'updates' in versions:
print('Updates: %s' % versions['updates'])
else: else:
print('Would copy to %s-updates' % release) print('Release: %s' % versions.get('release'))
else: if options.devel and 'devel' in versions:
if options.release: print('Devel: %s' % versions['devel'])
# -proposed -> release
copy(to_pocket='Release', to_series=release) copy = partial(
print('Copied to %s' % release) dst_archive.copyPackage, from_archive=src_archive,
else: include_binaries=True, source_name=pkg,
# -proposed -> -updates version=versions['proposed'], auto_approve=True)
# only phasing updates for >=raring to start
if (release not in ('lucid', 'precise') and if options.devel and not options.britney:
package != 'linux' and if ('devel' not in versions or
not package.startswith('linux-') and versions['devel'] in (
not options.security): versions.get('updates', 'notexisting'),
copy(to_pocket='Updates', to_series=release, versions['release'])):
phased_update_percentage=options.percentage) if not options.no_act:
copy(to_pocket='Proposed', to_series=devel_series.name)
print('Version in %s matches development series, '
'copied to %s-proposed' %
(release, devel_series.name))
else: else:
copy(to_pocket='Updates', to_series=release) print('ERROR: Version in %s does not match development '
print('Copied to %s-updates' % release) 'series, not copying' % release)
if not options.no_bugs:
sru_bugs = match_srubugs(options, versions['changesfile'])
tag = 'verification-needed-%s' % release
for sru_bug in sru_bugs:
if tag not in sru_bug.tags:
update_sru_bug(sru_bug, pkg)
# -proposed -> -security
if options.security:
if options.no_act: if options.no_act:
print('Would copy to %s-security' % release) if options.release:
print('Would copy to %s' % release)
else:
print('Would copy to %s-updates' % release)
else: else:
copy(to_pocket='Security', to_series=release) if options.release:
print('Copied to %s-security' % release) # -proposed -> release
copy(to_pocket='Release', to_series=release)
print('Copied to %s' % release)
else:
# -proposed -> -updates
if (package != 'linux' and
not package.startswith('linux-') and
not options.security):
if options.britney:
# We can opt in to use britney for the package copy
# instead of doing direct pocket copies.
packages_to_britney[pkg] = versions['proposed']
else:
copy(to_pocket='Updates', to_series=release,
phased_update_percentage=options.percentage)
print('Copied to %s-updates' % release)
else:
copy(to_pocket='Updates', to_series=release)
print('Copied to %s-updates' % release)
# -proposed -> -security
if options.security:
if options.no_act:
print('Would copy to %s-security' % release)
else:
copy(to_pocket='Security', to_series=release)
print('Copied to %s-security' % release)
# Write hints for britney to copy the selected packages
if options.britney and packages_to_britney:
release_package_via_britney(options, packages_to_britney)
# If everything went well, update the bugs
if not options.no_bugs:
for pkg_versions in pkg_versions_map.values():
for pkg, versions in pkg_versions.items():
sru_bugs = match_srubugs(options, versions['changesfile'])
tag = 'verification-needed-%s' % release
for sru_bug in sru_bugs:
if tag not in sru_bug.tags:
update_sru_bug(sru_bug, pkg)
def release_package_via_britney(options, packages):
'''Release selected packages via britney unblock hints.'''
hints_path = os.path.join(options.cache, 'hints-ubuntu-%s' % release)
hints_file = os.path.join(hints_path, 'sru-release')
# Checkout the hints branch
if not os.path.exists(hints_path):
cmd = ['bzr', 'checkout', '--lightweight',
BZR_HINT_BRANCH % release, hints_path]
else:
cmd = ['bzr', 'update', hints_path]
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
sys.stderr.write("Failed bzr %s for the hints branch at %s\n" %
(cmd[1], hints_path))
sys.exit(1)
# Create the hint with a timestamp comment
timestamp = time.time() # In python2 we can't use datetime.timestamp()
date = datetime.datetime.now().ctime()
unblock_string = '# %s %s\n' % (timestamp, date)
unblock_string += ''.join(['unblock %s/%s\n' % (pkg, ver)
for pkg, ver in packages.items()])
unblock_string += '\n'
# Update and commit the hint
with open(hints_file, 'a+') as f:
f.write(unblock_string)
cmd = ['bzr', 'commit', '-m', 'sru-release %s %s' %
(release, ' '.join(packages.keys()))]
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
sys.stderr.write('Failed to bzr commit to the hints file %s\n'
'Please investigate the local hint branch and '
'commit the required unblock entries as otherwise '
'your changes will be lost.\n' %
hints_file)
sys.exit(1)
print('Added hints for promotion in release %s of packages %s' %
(release, ' '.join(packages.keys())))
if __name__ == '__main__': if __name__ == '__main__':
@ -346,6 +413,13 @@ if __name__ == '__main__':
'--skip-package-group-check', action='store_true', default=False, '--skip-package-group-check', action='store_true', default=False,
help=('Skip the package set checks that require some packages ' help=('Skip the package set checks that require some packages '
'be released together')) 'be released together'))
parser.add_option(
'--britney', action='store_true', default=False,
help='Use britney for copying the packages over to -updates (only '
'works for regular package releases into updates)')
parser.add_option(
'-C', '--cache', default='~/.cache/sru-release',
help='Cache directory to be used for the britney hints checkout')
options, args = parser.parse_args() options, args = parser.parse_args()
@ -359,6 +433,13 @@ if __name__ == '__main__':
release = args.pop(0) release = args.pop(0)
packages = args packages = args
# XXX: we only want to instantiate KernelSeries if we suspect this is
# a kernel package, this is necessarily dirty, dirty, dirty.
kernel_checks = False
for package in packages:
if package.startswith('linux-') or package == 'linux':
kernel_checks = True
if not options.skip_package_group_check: if not options.skip_package_group_check:
try: try:
packages = check_package_sets(packages) packages = check_package_sets(packages)
@ -366,6 +447,14 @@ if __name__ == '__main__':
sys.stderr.write(e.args[0] + '\n') sys.stderr.write(e.args[0] + '\n')
sys.exit(1) sys.exit(1)
options.cache = os.path.expanduser(options.cache)
if not os.path.isdir(options.cache):
if os.path.exists(options.cache):
print('Cache path %s already exists and is not a directory.'
% options.cache)
sys.exit(1)
os.makedirs(options.cache)
launchpad = Launchpad.login_with( launchpad = Launchpad.login_with(
'ubuntu-archive-tools', options.launchpad_instance, version='devel') 'ubuntu-archive-tools', options.launchpad_instance, version='devel')
ubuntu = launchpad.distributions['ubuntu'] ubuntu = launchpad.distributions['ubuntu']
@ -375,12 +464,57 @@ if __name__ == '__main__':
sys.stderr.write( sys.stderr.write(
'WARNING: No current development series, -d will not work\n') 'WARNING: No current development series, -d will not work\n')
devel_series = None devel_series = None
if release == 'precise':
ks_source = None
if kernel_checks:
kernel_series = KernelSeries()
# See if we have a kernel-series record for this package. If we do
# then we are going to pivot to the routing therein.
ks_series = kernel_series.lookup_series(codename=release)
for ks_source_find in ks_series.sources:
for ks_package in ks_source_find.packages:
if ks_package.name == packages[0]:
ks_source = ks_source_find
break
# First confirm everything in this set we are attempting to release
# are indeed listed as valid for this kernel.
if ks_source is not None:
for package in packages:
if ks_source.lookup_package(package) is None:
sys.stderr.write(
'WARNING: {} not found in packages for kernel {}\n'.format(
package, ks_source.name))
if ks_source is None and release in ('precise', 'trusty'):
sys.stdout.write( sys.stdout.write(
'Called for precise; assuming kernel ESM publication\n') 'Called for {}; assuming kernel ESM publication\n'.format(release))
options.esm = True options.esm = True
if options.esm: # If we found a KernelSeries entry this has accurate routing information
# attached use that.
if ks_source is not None:
src_archive_ref, src_archive_pocket = ks_source.routing.lookup_destination('proposed', primary=True)
src_archive = launchpad.archives.getByReference(
reference=src_archive_ref)
dst_archive_ref, dst_archive_pocket = ks_source.routing.lookup_destination('updates', primary=True)
if dst_archive_ref == src_archive_ref:
dst_archive = src_archive
else:
dst_archive = launchpad.archives.getByReference(
reference=dst_archive_ref)
# Announce any non-standard archive routing.
if src_archive_ref != 'ubuntu':
print("Src Archive: {}".format(src_archive_ref))
if dst_archive_ref != 'ubuntu':
print("Dst Archive: {}".format(dst_archive_ref))
# --security is meaningless for private PPA publishing (XXX: currently true)
options.security = False
options.release = True
elif options.esm:
# --security is meaningless for ESM everything is a security update. # --security is meaningless for ESM everything is a security update.
options.security = False options.security = False
options.release = True options.release = True
@ -391,5 +525,4 @@ if __name__ == '__main__':
else: else:
src_archive = dst_archive = ubuntu.getArchive(name='primary') src_archive = dst_archive = ubuntu.getArchive(name='primary')
for package in packages: release_packages(options, packages)
release_package(options, package)

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Copyright (C) 2015 Brian Murray <brian.murray@canonical.com> # Copyright (C) 2015 Brian Murray <brian.murray@canonical.com>
@ -50,13 +50,19 @@ def parse_options():
help="release (default: %s)" % default_release) help="release (default: %s)" % default_release)
parser.add_option( parser.add_option(
"-p", "--package", dest="sourcepkg") "-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() opts, args = parser.parse_args()
if opts.reason not in ('ancient', 'failed'):
parser.error('Reason can only be "ancient" or "failed".')
return (opts, args) return (opts, args)
def process_bug(launchpad, distroseries, sourcepkg, num): def process_bug(launchpad, distroseries, sourcepkg, num, reason):
bug_target_re = re.compile( bug_target_re = re.compile(
r'/ubuntu/(?:(?P<suite>[^/]+)/)?\+source/(?P<source>[^/]+)$') r'/ubuntu/(?:(?P<suite>[^/]+)/)?\+source/(?P<source>[^/]+)$')
bug = launchpad.bugs[num] bug = launchpad.bugs[num]
@ -92,11 +98,19 @@ def process_bug(launchpad, distroseries, sourcepkg, num):
bug.tags = tags bug.tags = tags
bug.lp_save() bug.lp_save()
text = ('The version of %s in the proposed pocket of %s that was ' if reason == 'failed':
'purported to fix this bug report has been removed because ' text = ('The version of %s in the proposed pocket of %s that was '
'the bugs that were to be fixed by the upload were not ' 'purported to fix this bug report has been removed '
'verified in a timely (105 days) fashion.' % 'because one or more bugs that were to be fixed by the '
(sourcepkg, series_name.title())) '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, bug.newMessage(content=text,
subject='Proposed package removed from archive') subject='Proposed package removed from archive')
@ -117,12 +131,17 @@ def process_bug(launchpad, distroseries, sourcepkg, num):
if __name__ == '__main__': if __name__ == '__main__':
default_release = 'cosmic' default_release = 'focal'
removal_comment = ('The package was removed due to its SRU bug(s) '
'not being verified in a timely fashion.')
(opts, bugs) = parse_options() (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, launchpad = Launchpad.login_with('sru-remove', opts.launchpad_instance,
version="devel") version="devel")
ubuntu = launchpad.distributions['ubuntu'] ubuntu = launchpad.distributions['ubuntu']
@ -156,4 +175,4 @@ if __name__ == '__main__':
sys.exit(1) sys.exit(1)
# only comment on the bugs after removing the package # only comment on the bugs after removing the package
for bug in bugs: for bug in bugs:
process_bug(launchpad, series, opts.sourcepkg, bug) process_bug(launchpad, series, opts.sourcepkg, bug, opts.reason)

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python3
# Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd. # Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd.
# Authors: # Authors:
@ -159,6 +159,20 @@ def bug_open_js(bugs, title=None):
return '<button onclick="%s">%s (%i)</button>' % (js, title, len(bugs)) return '<button onclick="%s">%s (%i)</button>' % (js, title, len(bugs))
def verification_failed_check_for_removal(activities, release):
'''Helper function, checking if the verification-failed bug qualifies the
upload for removal'''
for activity in reversed(activities):
if (activity.whatchanged == 'tags' and
'verification-failed-%s' % release in activity.newvalue):
age = (datetime.datetime.now() - activity.datechanged.replace(
tzinfo=None)).days
if age >= 10:
return True
break
return False
def print_report(srus): def print_report(srus):
'''render the report''' '''render the report'''
global releases global releases
@ -192,6 +206,7 @@ def print_report(srus):
a.testing { color: blue; } a.testing { color: blue; }
a.broken { text-decoration: line-through; color: black; } a.broken { text-decoration: line-through; color: black; }
a.removal { color: gray; font-weight: bold } a.removal { color: gray; font-weight: bold }
a.blockproposed:after { content: "\\1F6A7"; font-weight: normal; }
</style> </style>
</head> </head>
<body> <body>
@ -229,7 +244,10 @@ bugs in <span style="font-style: italic">italic</span> are kernel tracking
bugs and bugs that are bugs and bugs that are
<span style="text-decoration: line-through;">struck through</span> are <span style="text-decoration: line-through;">struck through</span> are
duplicate bug reports or weren't accessible at the time the report was duplicate bug reports or weren't accessible at the time the report was
generated.</p>''') generated. Bugs with the &#x1F6A7; character next to their number are bugs with
a block-proposed-SERIES tag, indicating that they should not be released
without double-checking the bug contents. Those can be staged in -proposed,
even when verified, for a reason.</p>''')
# #
# pending SRUs # pending SRUs
@ -240,13 +258,14 @@ generated.</p>''')
pkgcleanup_release = [] pkgcleanup_release = []
pkgsuperseded = [] pkgsuperseded = []
# set of (series_name, srcpkg, [bugs]) # set of (series_name, srcpkg, [bugs])
proposed_antique = [] proposed_ancient = []
proposed_failed = []
for release in sorted(srus): for release in sorted(srus):
if not srus[release]: if not srus[release]:
continue continue
for pack in srus[release]: for pack in srus[release]:
pkg_index[release][pack] = srus[release][pack]['published'] pkg_index[release][pack] = srus[release][pack]['published']
for pkg, pub in sorted(pkg_index[release].iteritems(), for pkg, pub in sorted(pkg_index[release].items(),
key=itemgetter(1)): key=itemgetter(1)):
rpkg = srus[release][pkg] rpkg = srus[release][pkg]
if cleanup(rpkg): if cleanup(rpkg):
@ -273,20 +292,26 @@ generated.</p>''')
<tr><th>Package</th><th>-release</th><th>-updates</th> <tr><th>Package</th><th>-release</th><th>-updates</th>
<th>-proposed (signer, creator)</th> <th>-proposed (signer, creator)</th>
<th>changelog bugs</th><th>days</th></tr>''' % (release, release)) <th>changelog bugs</th><th>days</th></tr>''' % (release, release))
for pkg, pub in sorted(pkg_index[release].iteritems(), for pkg, pub in sorted(pkg_index[release].items(),
key=itemgetter(1)): key=itemgetter(1)):
# skip everything that shows up on the kernel SRU reports # skip everything that shows up on the kernel SRU reports
if (pkg in ('linux', 'linux-hwe', 'linux-hwe-edge', if (pkg in ('linux', 'linux-hwe', 'linux-hwe-edge', 'linux-hwe-5.0',
'linux-kvm', 'linux-oem', 'linux-kvm', 'linux-oem', 'linux-oem-osp1',
'linux-raspi2', 'linux-snapdragon', 'linux-raspi2', 'linux-raspi2-5.3',
'linux-snapdragon', 'linux-bluefield',
'linux-keystone', 'linux-armadaxp', 'linux-ti-omap4', 'linux-keystone', 'linux-armadaxp', 'linux-ti-omap4',
'linux-aws', 'linux-aws-hwe', 'linux-aws-edge', 'linux-aws', 'linux-aws-5.0', 'linux-aws-5.3',
'linux-azure', 'linux-azure-edge', 'linux-aws-hwe', 'linux-aws-edge',
'linux-gcp', 'linux-gcp-edge', 'linux-azure', 'linux-azure-edge', 'linux-azure-5.3',
'linux-gke', 'linux-euclid', 'linux-oracle') or 'linux-gcp', 'linux-gcp-5.3', 'linux-gcp-edge',
'linux-gke', 'linux-gke-4.15', 'linux-gke-5.0',
'linux-gke-5.3',
'linux-euclid', 'linux-oracle', 'linux-oracle-5.0',
'linux-oracle-5.3') or
pkg.startswith('linux-signed') or pkg.startswith('linux-signed') or
pkg.startswith('linux-meta') or pkg.startswith('linux-meta') or
pkg.startswith('linux-lts') or pkg.startswith('linux-lts') or
pkg.startswith('linux-restricted-modules') or
pkg.startswith('linux-backports-modules')): pkg.startswith('linux-backports-modules')):
continue continue
# for langpack updates, only keep -en as a representative # for langpack updates, only keep -en as a representative
@ -331,14 +356,17 @@ generated.</p>''')
(pkgurl + rpkg['proposed'], rpkg['proposed'], uploaders)) (pkgurl + rpkg['proposed'], rpkg['proposed'], uploaders))
print(' <td>') print(' <td>')
removable = True removable = True
antique = False ancient = False
for b, t in sorted(rpkg['bugs'].iteritems()): failed_and_removable = False
for b, t in sorted(rpkg['bugs'].items()):
cls = ' class="' cls = ' class="'
incomplete = False incomplete = False
activities = None
try: try:
bug = lp.bugs[b] bug = lp.bugs[b]
bug_title = bug.title.encode('UTF-8') bug_title = bug.title
hover_text = bug_title hover_text = bug_title
activities = bug.activity
for task in bug.bug_tasks: for task in bug.bug_tasks:
if task.self_link.split('/')[4] != 'ubuntu': if task.self_link.split('/')[4] != 'ubuntu':
continue continue
@ -362,6 +390,12 @@ generated.</p>''')
elif ('verification-failed' in t or elif ('verification-failed' in t or
'verification-failed-%s' % release in t): 'verification-failed-%s' % release in t):
cls += ' verificationfailed' cls += ' verificationfailed'
# Check if the SRU was in verification-failed for long
# enough to be considered for removal.
if not failed_and_removable and activities:
failed_and_removable = \
verification_failed_check_for_removal(
activities, release)
elif 'verification-done-%s' % release in t: elif 'verification-done-%s' % release in t:
cls += ' verified' cls += ' verified'
removable = False removable = False
@ -387,7 +421,7 @@ generated.</p>''')
if (m_date.replace(tzinfo=None) < today if (m_date.replace(tzinfo=None) < today
- datetime.timedelta(16)): - datetime.timedelta(16)):
cls += ' removal' cls += ' removal'
antique = True ancient = True
continue continue
if 'messages' in cls: if 'messages' in cls:
cls = cls.replace('messages', '') cls = cls.replace('messages', '')
@ -401,23 +435,29 @@ generated.</p>''')
bug_title, \ bug_title, \
datetime.datetime.strftime( datetime.datetime.strftime(
m_date, '%Y-%m-%d')) m_date, '%Y-%m-%d'))
hover_text += message.content.encode( hover_text += message.content + ' - '
'UTF-8') + ' - ' hover_text += m_owner.name
hover_text += m_owner.name.encode( ancient = False
'UTF-8')
antique = False
except ClientError as error: except ClientError as error:
# people who don't use lp anymore # people who don't use lp anymore
if error.response['status'] == '410': if error.response['status'] == '410':
continue continue
# We now also try handling block-proposed tags for updates
# that can be verified but should not be released yet for
# some reasons.
if 'block-proposed-%s' % release in t:
cls += ' blockproposed'
cls += '"' cls += '"'
print('<a href="%s/bugs/%d" ' print('<a href="%s/bugs/%d" '
'title="%s" %s>%d%s</a>' % 'title="%s" %s>%d%s</a>' %
(lp_url, b, hover_text.replace('"', ''), cls, b, (lp_url, b, hover_text.replace('"', ''), cls, b,
'(hw)' if 'hw-specific' in t else '')) '(hw)' if 'hw-specific' in t else ''))
if antique and removable: if failed_and_removable:
proposed_antique.append((releases[release].name, pkg, proposed_failed.append((releases[release].name, pkg,
[str(b) for b in rpkg['bugs']]))
elif ancient and removable:
proposed_ancient.append((releases[release].name, pkg,
[str(b) for b in rpkg['bugs']])) [str(b) for b in rpkg['bugs']]))
print('&nbsp;</td>') print('&nbsp;</td>')
print(' <td>%i</td></tr>' % age) print(' <td>%i</td></tr>' % age)
@ -501,12 +541,24 @@ generated.</p>''')
'-e %s %s' % (r, pkg[2]['proposed'], pkg[1])) '-e %s %s' % (r, pkg[2]['proposed'], pkg[1]))
print('</pre>') print('</pre>')
print('<p>The following packages have bugs that have failed '
'verification for more than 10 days and should be removed from '
'-proposed:</p>')
print('<pre>')
for r in releases:
for pkg in sorted(proposed_failed):
if pkg[0].startswith(r):
print('sru-remove --reason=failed -s %s -p %s %s' %
(r, pkg[1], ' '.join(pkg[2])))
print('</pre>')
print('<p>The following packages have not had their SRU bugs verified in ' print('<p>The following packages have not had their SRU bugs verified in '
'105 days and should be removed from -proposed:</p>') '105 days and should be removed from -proposed:</p>')
print('<pre>') print('<pre>')
for r in releases: for r in releases:
for pkg in sorted(proposed_antique): for pkg in sorted(proposed_ancient):
if pkg[0].startswith(r): if pkg[0].startswith(r):
print('sru-remove -s %s -p %s %s' % print('sru-remove -s %s -p %s %s' %
(r, pkg[1], ' '.join(pkg[2]))) (r, pkg[1], ' '.join(pkg[2])))
@ -554,7 +606,7 @@ def match_srubugs(changesfileurls):
changelog = urlopen(changesfileurl) changelog = urlopen(changesfileurl)
bugnums = [] bugnums = []
for l in changelog: for l in changelog:
if l.startswith('Launchpad-Bugs-Fixed: '): if l.startswith(b'Launchpad-Bugs-Fixed: '):
bugnums = [int(b) for b in l.split()[1:]] bugnums = [int(b) for b in l.split()[1:]]
break break
@ -630,7 +682,7 @@ def get_srus():
if release != 'lucid': if release != 'lucid':
excuses_page = excuses_url % release excuses_page = excuses_url % release
excuses = urlopen(excuses_page) excuses = urlopen(excuses_page)
excuses_data = yaml.load(excuses) excuses_data = yaml.load(excuses, Loader=yaml.CSafeLoader)
pkg_excuses = [excuse['source'] pkg_excuses = [excuse['source']
for excuse in excuses_data['sources'] for excuse in excuses_data['sources']
if 'autopkgtest' in excuse['reason'] if 'autopkgtest' in excuse['reason']
@ -708,6 +760,10 @@ def bugs_from_changes(change_url):
def main(): def main():
# Force encoding to UTF-8 even in non-UTF-8 locales.
import io, sys
sys.stdout = io.TextIOWrapper(
sys.stdout.detach(), encoding="UTF-8", line_buffering=True)
logging.basicConfig(level=DEBUGLEVEL, logging.basicConfig(level=DEBUGLEVEL,
format="%(asctime)s - %(levelname)s - %(message)s") format="%(asctime)s - %(levelname)s - %(message)s")
lpinit() lpinit()

@ -35,9 +35,10 @@ import subprocess
import sys import sys
import tempfile import tempfile
try: try:
from urllib.parse import quote
from urllib.request import urlopen, urlretrieve from urllib.request import urlopen, urlretrieve
except ImportError: except ImportError:
from urllib import urlopen, urlretrieve from urllib import urlopen, urlretrieve, quote
import webbrowser import webbrowser
from contextlib import ExitStack from contextlib import ExitStack
@ -126,7 +127,7 @@ def from_queue(options, archive, sourcepkg, series, version=None):
queue = options.queue.title() queue = options.queue.title()
queue_url = ('https://launchpad.net/ubuntu/%s/+queue?' queue_url = ('https://launchpad.net/ubuntu/%s/+queue?'
'queue_state=%s&batch=300&queue_text=%s' % 'queue_state=%s&batch=300&queue_text=%s' %
(series.name, queues[queue], sourcepkg)) (series.name, queues[queue], quote(sourcepkg)))
uploads = [upload for upload in uploads = [upload for upload in
series.getPackageUploads(archive=archive, exact_match=True, series.getPackageUploads(archive=archive, exact_match=True,
name=sourcepkg, pocket='Proposed', name=sourcepkg, pocket='Proposed',
@ -255,7 +256,7 @@ def reject_comment(launchpad, num, package, release, reason):
if __name__ == '__main__': if __name__ == '__main__':
default_release = 'cosmic' default_release = 'focal'
ppa_url = ('https://launchpad.net/~%s/+archive/ubuntu/%s/+packages?' ppa_url = ('https://launchpad.net/~%s/+archive/ubuntu/%s/+packages?'
'field.series_filter=%s') 'field.series_filter=%s')

@ -74,6 +74,13 @@ def process_bug(launchpad, sourcepkg, version, release, num):
'Is there anything left to sponsor?' % num) 'Is there anything left to sponsor?' % num)
if not sourcepkg or 'linux' not in sourcepkg: if not sourcepkg or 'linux' not in sourcepkg:
block_proposed_series = 'block-proposed-%s' % release
if block_proposed_series in bug.tags:
print('The %s tag is still set on bug LP: #%s. '
'Should the package continue to be blocked in proposed? '
'Please investigate and adjust the tags accordingly.'
% (block_proposed_series, num))
# this dance is needed due to # this dance is needed due to
# https://bugs.launchpad.net/launchpadlib/+bug/254901 # https://bugs.launchpad.net/launchpadlib/+bug/254901
btags = bug.tags btags = bug.tags
@ -124,7 +131,8 @@ def process_bug(launchpad, sourcepkg, version, release, num):
text += (' Your feedback will aid us getting this update out to other ' text += (' Your feedback will aid us getting this update out to other '
'Ubuntu users.\n\nIf this package fixes the bug for you, ' 'Ubuntu users.\n\nIf this package fixes the bug for you, '
'please add a comment to this bug, mentioning the version of the ' 'please add a comment to this bug, mentioning the version of the '
'package you tested and change the tag from ' 'package you tested, what testing has been performed on the '
'package and change the tag from '
'verification-needed-%s to verification-done-%s. ' 'verification-needed-%s to verification-done-%s. '
'If it does not fix the bug for you, please add a comment ' 'If it does not fix the bug for you, please add a comment '
'stating that, and change the tag to verification-failed-%s. ' 'stating that, and change the tag to verification-failed-%s. '

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python2.7
# Copyright (C) 2011 Iain Lane # Copyright (C) 2011 Iain Lane
# Copyright (C) 2011 Stefano Rivera # Copyright (C) 2011 Stefano Rivera

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
DIST="${DIST:-disco}" DIST="${DIST:-groovy}"
MADISON="$(rmadison -a source -s "$DIST" "$1")" MADISON="$(rmadison -a source -s "$DIST" "$1")"
[ "$MADISON" ] || exit 1 [ "$MADISON" ] || exit 1
@ -51,7 +51,7 @@ wget -q -O- http://changelogs.ubuntu.com/changelogs/pool/$SECTION/$POOLINDEX/$1/
target="${target%;}" target="${target%;}"
target="${target%%-*}" target="${target%%-*}"
case $target in case $target in
warty|hoary|breezy|dapper|edgy|feisty|gutsy|hardy|intrepid|jaunty|karmic|lucid|maverick|natty|oneiric|precise|quantal|raring|saucy|trusty|utopic|vivid|wily|xenial|yakkety|zesty|artful|bionic|cosmic|disco|devel) warty|hoary|breezy|dapper|edgy|feisty|gutsy|hardy|intrepid|jaunty|karmic|lucid|maverick|natty|oneiric|precise|quantal|raring|saucy|trusty|utopic|vivid|wily|xenial|yakkety|zesty|artful|bionic|cosmic|disco|eoan|focal|groovy|devel)
;; ;;
*) *)
exit 0 exit 0

@ -0,0 +1,139 @@
#!/usr/bin/python3
# Copyright (C) 2020 Canonical Ltd.
# Author: Steve Langasek <steve.langasek@canonical.com>
# 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/>.
'''Synchronize the i386 source package whitelist in Launchpad with the output
of germinate.
USAGE:
update-i386-whitelist [--dry-run] https://people.canonical.com/~ubuntu-archive/germinate-output/i386.focal/i386+build-depends.sources
'''
from launchpadlib.launchpad import Launchpad
import optparse
from urllib.request import urlopen
import sys
def get_sources_from_url(url):
'''Download the germinate output and parse out the list of sources.
Returns list of source package names.
'''
sources = []
file = urlopen(url)
for i in file:
if i.startswith(b'Source') or i.startswith(b'---'):
continue
sources.append(i.decode('utf-8').split(' ',maxsplit=1)[0])
return sources
def parse_options():
'''Parse command line arguments.
Return (options, source_package) tuple.
'''
parser = optparse.OptionParser(
usage='Usage: %prog [--dry-run] https://people.canonical.com/~ubuntu-archive/germinate-output/i386.focal/i386+build-depends.sources')
parser.add_option(
"--dry-run", help="don't change launchpad, just report the delta",
action="store_true")
parser.add_option(
"-s", dest="release", default=default_release, metavar="RELEASE",
help="release (default: %s)" % default_release)
(opts, args) = parser.parse_args()
if len(args) != 1:
parser.error('Need to specify a URL to sync from')
return (opts, args[0])
if __name__ == '__main__':
default_release = 'focal'
(opts, url) = parse_options()
launchpad = Launchpad.login_with('update-i386-whitelist',
'production',
version="devel")
ubuntu = launchpad.distributions['ubuntu']
series = ubuntu.getSeries(name_or_version=opts.release)
archive = ubuntu.main_archive
sources = get_sources_from_url(url)
packageset = launchpad.packagesets.getByName(name='i386-whitelist',
distroseries=series)
currentSet = set(packageset.getSourcesIncluded())
newSet = set(sources)
# hard-coded list of ppa-only additions; can maybe go away when
# https://bugs.launchpad.net/launchpad/+bug/1855069 is fixed, but this is
# also potentially useful for bootstrapping any additional packages into
# the archive if needed.
# bootstrap new spdlog dep
newSet.update(['fmtlib'])
# for new lintian
newSet.update(['libdevel-size-perl', 'libcpanel-json-xs-perl',
'libsereal-decoder-perl', 'libsereal-encoder-perl',
'libjson-xs-perl'])
# needed to bootstrap openjdk-N
newSet.update(['openjdk-12'])
newSet.update(['openjdk-13'])
newSet.update(['openjdk-14'])
newSet.update(['openjdk-15'])
newSet.update(['openjdk-8'])
# we get the wrong answer from germinate about a source package's
# whitelisting when the package provides both Arch: any and Arch: all
# binaries but we actually only want the Arch: all ones. Rather than
# fix this in germinate, for now just manually exclude the packages
# we've found that have this problem.
for pkg in ('frei0r', 'xorg', 'ubuntu-drivers-common'):
try:
newSet.remove(pkg)
except KeyError:
pass
print("Additions:" )
additions = list(newSet-currentSet)
additions.sort()
for i in additions:
print(" * %s" % i)
print("Removals:" )
removals = list(currentSet-newSet)
removals.sort()
for i in removals:
print(" * %s" % i)
if opts.dry_run:
print("--dry-run is set, doing nothing.")
sys.exit(0)
if additions or removals:
print("Commit changes to the packageset? [yN] ", end="")
sys.stdout.flush()
response = sys.stdin.readline()
if not response.strip().lower().startswith('y'):
sys.exit(1)
if additions:
packageset.addSources(names=additions)
if removals:
packageset.removeSources(names=removals)

@ -0,0 +1,55 @@
#!/usr/bin/python2.7
# Copyright (C) 2019 Canonical Ltd.
# Author: Brian Murray <brian.murray@canonical.com>
# 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."""
import gzip
import os
import tempfile
import apt_pkg
def read_tag_file(path, pkg=None):
tmp = tempfile.NamedTemporaryFile(prefix='checkrdepends.', delete=False)
try:
compressed = gzip.open(path)
try:
tmp.write(compressed.read())
finally:
compressed.close()
tmp.close()
with open(tmp.name) as uncompressed:
tag_file = apt_pkg.TagFile(uncompressed)
prev_name = None
prev_stanza = None
for stanza in tag_file:
try:
name = stanza['package']
except KeyError:
continue
if pkg:
if name != pkg:
continue
if name != prev_name and prev_stanza is not None:
yield prev_stanza
prev_name = name
prev_stanza = stanza
if prev_stanza is not None:
yield prev_stanza
finally:
os.unlink(tmp.name)
Loading…
Cancel
Save