You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

459 lines
20 KiB

#!/usr/bin/python3
# Copyright (C) 2011, 2012 Canonical Ltd.
# Author: Martin Pitt <martin.pitt@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/>.
'''Copy a kernel from the kernel team's PPA to -proposed.
USAGE:
copy-proposed-kernel [--security] <release> <sourcepackage>
'''
from __future__ import print_function
import argparse
from contextlib import contextmanager
from copy import copy
from io import StringIO
import sys
import unittest
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.set_defaults(testing=False)
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('--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('--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('--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('source', action='store', nargs='+', help='The source package name')
args = parser.parse_args()
if args.esm or args.fips or args.ibmgt:
print("NOTE: flags --esm, --fips, and --ibmgt are now deprecated")
release = args.series
ks = KernelSeries()
launchpad = Launchpad.login_with(
'ubuntu-archive-tools', 'production', version='devel')
ubuntu = launchpad.distributions['ubuntu']
distro_series = ubuntu.getSeries(name_or_version=release)
copies = []
for pkg in list(args.source):
# BODGE: routing should just take release/pkg.
args.source = pkg
(from_archive, to_archive, security) = routing(args, ks)
##print("from_archive<{}> to_archive<{}>".format(from_archive, to_archive))
if from_archive is None:
print("ERROR: bad source PPA")
sys.exit(1)
if to_archive is None:
print("ERROR: bad destination")
sys.exit(1)
(from_reference, from_pocket) = from_archive
(to_reference, to_pocket) = to_archive
# Grab a reference to the 'from' archive.
from_archive = launchpad.archives.getByReference(
reference=from_reference)
# 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:
print("Dry run; no packages copied.")
sys.exit(0)
for copy in copies:
# We found valid packages for each requested element, actually copy them.
to_archive.copyPackage(**copy)
# TODO: adjust this script to use find-bin-overrides or rewrite
# find-bin-overrides to use lpapi and use it here.
print('''
IMPORTANT: Please verify the overrides are correct for this source package.
Failure to do so may result in uninstallability when it is ultimately copied to
-updates/-security. lp:ubuntu-qa-tools/security-tools/find-bin-overrides can
help with this.
''')