Merge remote-tracking branch 'vorlon/ubuntu-build-revamp'

This commit is contained in:
Gianfranco Costamagna 2024-03-12 10:36:13 +01:00
commit 0ec53180f2
2 changed files with 211 additions and 122 deletions

View File

@ -2,16 +2,16 @@
# #
# ubuntu-build - command line interface for Launchpad buildd operations. # ubuntu-build - command line interface for Launchpad buildd operations.
# #
# Copyright (C) 2007 Canonical Ltd. # Copyright (C) 2007-2024 Canonical Ltd.
# Authors: # Authors:
# - Martin Pitt <martin.pitt@canonical.com> # - Martin Pitt <martin.pitt@canonical.com>
# - Jonathan Davies <jpds@ubuntu.com> # - Jonathan Davies <jpds@ubuntu.com>
# - Michael Bienia <geser@ubuntu.com> # - Michael Bienia <geser@ubuntu.com>
# - Steve Langasek <steve.langasek@canonical.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
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, version 3 of the License.
# (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
@ -29,19 +29,63 @@ import argparse
import sys import sys
from launchpadlib.credentials import TokenAuthorizationException from launchpadlib.credentials import TokenAuthorizationException
from launchpadlib.launchpad import Launchpad
import lazr.restfulclient.errors
from ubuntutools import getLogger from ubuntutools import getLogger
from ubuntutools.lp.lpapicache import Distribution, Launchpad, PersonTeam from ubuntutools.lp.udtexceptions import PocketDoesNotExistError
from ubuntutools.lp.udtexceptions import (
PackageNotFoundException,
PocketDoesNotExistError,
SeriesNotFoundException,
)
from ubuntutools.misc import split_release_pocket from ubuntutools.misc import split_release_pocket
Logger = getLogger() Logger = getLogger()
def getBuildStates(pkg, archs):
res = []
for build in pkg.getBuilds():
if build.arch_tag in archs:
res.append(f" {build.arch_tag}: {build.buildstate}")
msg = "\n".join(res)
return f"Build state(s) for '{pkg.source_package_name}':\n{msg}"
def rescoreBuilds(pkg, archs, score):
res = []
for build in pkg.getBuilds():
arch = build.arch_tag
if arch in archs:
if not build.can_be_rescored:
continue
try:
build.rescore(score=score)
res.append(f" {arch}: done")
except lazr.restfulclient.errors.Unauthorized:
Logger.error(
"You don't have the permissions to rescore builds. Ignoring your rescore request."
)
return None
except lazr.restfulclient.errors.BadRequest:
Logger.info("Cannot rescore build of %s on %s.",
build.source_package_name, arch)
res.append(f" {arch}: failed")
msg = "\n".join(res)
return f"Rescoring builds of '{pkg.source_package_name}' to {score}:\n{msg}"
def retryBuilds(pkg, archs):
res = []
for build in pkg.getBuilds():
arch = build.arch_tag
if arch in archs:
try:
build.retry()
res.append(f" {arch}: done")
except lazr.restfulclient.errors.BadRequest:
res.append(f" {arch}: failed")
msg = "\n".join(res)
return f"Retrying builds of '{pkg.source_package_name}':\n{msg}"
def main(): def main():
# Usage. # Usage.
usage = "%(prog)s <srcpackage> <release> <operation>\n\n" usage = "%(prog)s <srcpackage> <release> <operation>\n\n"
@ -65,12 +109,7 @@ def main():
# Prepare our option parser. # Prepare our option parser.
parser = argparse.ArgumentParser(usage=usage) parser = argparse.ArgumentParser(usage=usage)
# Retry options parser.add_argument(
retry_rescore_options = parser.add_argument_group(
"Retry and rescore options",
"These options may only be used with the 'retry' and 'rescore' operations.",
)
retry_rescore_options.add_argument(
"-a", "-a",
"--arch", "--arch",
action="append", action="append",
@ -79,6 +118,9 @@ def main():
f"include: {', '.join(valid_archs)}.", f"include: {', '.join(valid_archs)}.",
) )
parser.add_argument("-A", "--archive", help="operate on ARCHIVE",
default="ubuntu")
# Batch processing options # Batch processing options
batch_options = parser.add_argument_group( batch_options = parser.add_argument_group(
"Batch processing", "Batch processing",
@ -106,25 +148,35 @@ def main():
help="Rescore builds to <priority>.", help="Rescore builds to <priority>.",
) )
batch_options.add_argument( batch_options.add_argument(
"--arch2", "--state", action="store", dest="state",
action="append", help="Act on builds that are in the specified state",
dest="architecture",
help=f"Affect only 'architecture' (can be used several times)."
f" Valid architectures are: {', '.join(valid_archs)}.",
) )
parser.add_argument("packages", metavar="package", nargs="+", help=argparse.SUPPRESS)
parser.add_argument("packages", metavar="package", nargs="*", help=argparse.SUPPRESS)
# Parse our options. # Parse our options.
args = parser.parse_args() args = parser.parse_args()
try: launchpad = Launchpad.login_with("ubuntu-dev-tools", "production",
# Will fail here if we have no credentials, bail out version="devel")
Launchpad.login() me = launchpad.me
except TokenAuthorizationException:
sys.exit(1)
me = PersonTeam.me
if not args.batch: ubuntu = launchpad.distributions['ubuntu']
if args.batch:
release = args.series
if not release:
# ppas don't have a proposed pocket so just use the release pocket;
# but for the main archive we default to -proposed
release = ubuntu.getDevelopmentSeries()[0].name
if args.archive == 'ubuntu':
release = release + "-proposed"
try:
(release, pocket) = split_release_pocket(release)
except PocketDoesNotExistError as error:
Logger.error(error)
sys.exit(1)
else:
# Check we have the correct number of arguments. # Check we have the correct number of arguments.
if len(args.packages) < 3: if len(args.packages) < 3:
parser.error("Incorrect number of arguments.") parser.error("Incorrect number of arguments.")
@ -137,6 +189,14 @@ def main():
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
archive = launchpad.archives.getByReference(reference=args.archive)
try:
distroseries = ubuntu.getSeries(name_or_version=release)
except lazr.restfulclient.errors.NotFound as error:
Logger.error(error)
sys.exit(1)
if not args.batch:
# Check our operation. # Check our operation.
if operation not in ("rescore", "retry", "status"): if operation not in ("rescore", "retry", "status"):
Logger.error("Invalid operation: %s.", operation) Logger.error("Invalid operation: %s.", operation)
@ -160,36 +220,37 @@ def main():
Logger.error(error) Logger.error(error)
sys.exit(1) sys.exit(1)
ubuntu_archive = Distribution("ubuntu").getArchive()
# Get list of published sources for package in question. # Get list of published sources for package in question.
try: try:
sources = ubuntu_archive.getSourcePackage(package, release, pocket) sources = archive.getPublishedSources(
distroseries = Distribution("ubuntu").getSeries(release) distro_series=distroseries,
except (SeriesNotFoundException, PackageNotFoundException) as error: exact_match=True,
Logger.error(error) pocket=pocket,
source_name=package,
status='Published')[0]
except IndexError as error:
Logger.error("No publication found for package %s", package)
sys.exit(1) sys.exit(1)
# Get list of builds for that package. # Get list of builds for that package.
builds = sources.getBuilds() builds = sources.getBuilds()
# Find out the version and component in given release. # Find out the version and component in given release.
version = sources.getVersion() version = sources.source_package_version
component = sources.getComponent() component = sources.component_name
# Operations that are remaining may only be done by Ubuntu developers # Operations that are remaining may only be done by Ubuntu developers
# (retry) or buildd admins (rescore). Check if the proper permissions # (retry) or buildd admins (rescore). Check if the proper permissions
# are in place. # are in place.
if operation == "rescore":
necessary_privs = me.isLpTeamMember("launchpad-buildd-admins")
if operation == "retry": if operation == "retry":
necessary_privs = me.canUploadPackage( necessary_privs = archive.checkUpload(
ubuntu_archive, component=sources.getComponent(),
distroseries, distroseries=distroseries,
sources.getPackageName(), person=launchpad.me,
sources.getComponent(),
pocket=pocket, pocket=pocket,
sourcepackagename=sources.getPackageName(),
) )
if operation in ("rescore", "retry") and not necessary_privs: if operation == "retry" and not necessary_privs:
Logger.error( Logger.error(
"You cannot perform the %s operation on a %s package as you" "You cannot perform the %s operation on a %s package as you"
" do not have the permissions to do this action.", " do not have the permissions to do this action.",
@ -223,7 +284,13 @@ def main():
# FIXME: make priority an option # FIXME: make priority an option
priority = 5000 priority = 5000
Logger.info("Rescoring build %s to %d...", build.arch_tag, priority) Logger.info("Rescoring build %s to %d...", build.arch_tag, priority)
try:
build.rescore(score=priority) build.rescore(score=priority)
except lazr.restfulclient.errors.Unauthorized:
Logger.error(
"You don't have the permissions to rescore builds. Ignoring your rescore request."
)
break
else: else:
Logger.info("Cannot rescore build on %s.", build.arch_tag) Logger.info("Cannot rescore build on %s.", build.arch_tag)
if operation == "retry": if operation == "retry":
@ -252,64 +319,131 @@ def main():
# filter out duplicate and invalid architectures # filter out duplicate and invalid architectures
archs.intersection_update(valid_archs) archs.intersection_update(valid_archs)
release = args.series if not args.packages:
if not release: retry_count = 0
release = Distribution("ubuntu").getDevelopmentSeries().name + "-proposed" can_rescore = True
try:
(release, pocket) = split_release_pocket(release)
except PocketDoesNotExistError as error:
Logger.error(error)
sys.exit(1)
ubuntu_archive = Distribution("ubuntu").getArchive() if not args.state:
try: if args.retry:
distroseries = Distribution("ubuntu").getSeries(release) args.state='Failed to build'
except SeriesNotFoundException as error: elif args.priority:
Logger.error(error) args.state='Needs building'
sys.exit(1) # there is no equivalent to series.getBuildRecords() for a ppa.
# however, we don't want to have to traverse all build records for
# all series when working on the main archive, so we use
# series.getBuildRecords() for ubuntu and handle ppas separately
series = ubuntu.getSeries(name_or_version=release)
if args.archive == 'ubuntu':
builds = series.getBuildRecords(build_state=args.state,
pocket=pocket)
else:
builds = []
for build in archive.getBuildRecords(build_state=args.state,
pocket=pocket):
if not build.current_source_publication:
continue
if build.current_source_publication.distro_series==series:
builds.append(build)
for build in builds:
if build.arch_tag not in archs:
continue
if not build.current_source_publication:
continue
# fixme: refactor
# Check permissions (part 2): check upload permissions for the
# source package
can_retry = args.retry and archive.checkUpload(
component=build.current_source_publication.component_name,
distroseries=series,
person=launchpad.me,
pocket=pocket,
sourcepackagename=build.source_package_name,
)
if args.retry and not can_retry:
Logger.error(
"You don't have the permissions to retry the "
"build of '%s', skipping.",
build.source_package_name
)
continue
Logger.info(
"The source version for '%s' in '%s' (%s) is: %s",
build.source_package_name,
release,
pocket,
build.source_package_version
)
# Check permisions (part 1): Rescoring can only be done by buildd admins if args.retry:
can_rescore = args.priority and me.isLpTeamMember("launchpad-buildd-admins") Logger.info("Retrying build of %s on %s...",
if args.priority and not can_rescore: build.source_package_name, build.arch_tag)
retry_count += 1
build.retry()
if args.priority and can_rescore:
if build.can_be_rescored:
try:
build.rescore(score=args.priority)
except lazr.restfulclient.errors.Unauthorized:
Logger.error( Logger.error(
"You don't have the permissions to rescore builds. Ignoring your rescore request." "You don't have the permissions to rescore builds. Ignoring your rescore request."
) )
can_rescore = False
except lazr.restfulclient.errors.BadRequest:
Logger.info("Cannot rescore build of %s on %s.",
build.source_package_name, build.arch_tag)
Logger.info("")
if args.retry:
Logger.info("%d package builds retried", retry_count)
sys.exit(0)
for pkg in args.packages: for pkg in args.packages:
try: try:
pkg = ubuntu_archive.getSourcePackage(pkg, release, pocket) pkg = archive.getPublishedSources(
except PackageNotFoundException as error: distro_series=distroseries,
Logger.error(error) exact_match=True,
pocket=pocket,
source_name=pkg,
status='Published')[0]
except IndexError as error:
Logger.error("No publication found for package %s", pkg)
continue continue
# Check permissions (part 2): check upload permissions for the source # Check permissions (part 2): check upload permissions for the source
# package # package
can_retry = args.retry and me.canUploadPackage( can_retry = args.retry and archive.checkUpload(
ubuntu_archive, distroseries, pkg.getPackageName(), pkg.getComponent() component=pkg.component_name,
distroseries=distroseries,
person=launchpad.me,
pocket=pocket,
sourcepackagename=pkg.source_package_name,
) )
if args.retry and not can_retry: if args.retry and not can_retry:
Logger.error( Logger.error(
"You don't have the permissions to retry the " "You don't have the permissions to retry the "
"build of '%s'. Ignoring your request.", "build of '%s'. Ignoring your request.",
pkg.getPackageName(), pkg.source_package_name,
) )
Logger.info( Logger.info(
"The source version for '%s' in '%s' (%s) is: %s", "The source version for '%s' in '%s' (%s) is: %s",
pkg.getPackageName(), pkg.source_package_name,
release, release,
pocket, pocket,
pkg.getVersion(), pkg.source_package_version,
) )
Logger.info(pkg.getBuildStates(archs)) Logger.info(getBuildStates(pkg, archs))
if can_retry: if can_retry:
Logger.info(pkg.retryBuilds(archs)) Logger.info(retryBuilds(pkg, archs))
if args.priority and can_rescore: if args.priority:
Logger.info(pkg.rescoreBuilds(archs, args.priority)) Logger.info(rescoreBuilds(pkg, archs, args.priority))
Logger.info("") Logger.info("")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -1097,51 +1097,6 @@ class SourcePackagePublishingHistory(BaseWrapper):
for build in builds: for build in builds:
self._builds[build.arch_tag] = Build(build) self._builds[build.arch_tag] = Build(build)
def getBuildStates(self, archs):
res = []
if not self._builds:
self._fetch_builds()
for arch in archs:
build = self._builds.get(arch)
if build:
res.append(f" {build}")
msg = "\n".join(res)
return f"Build state(s) for '{self.getPackageName()}':\n{msg}"
def rescoreBuilds(self, archs, score):
res = []
if not self._builds:
self._fetch_builds()
for arch in archs:
build = self._builds.get(arch)
if build:
if build.rescore(score):
res.append(f" {arch}: done")
else:
res.append(f" {arch}: failed")
msg = "\n".join(res)
return f"Rescoring builds of '{self.getPackageName()}' to {score}:\n{msg}"
def retryBuilds(self, archs):
res = []
if not self._builds:
self._fetch_builds()
for arch in archs:
build = self._builds.get(arch)
if build:
if build.retry():
res.append(f" {arch}: done")
else:
res.append(f" {arch}: failed")
msg = "\n".join(res)
return f"Retrying builds of '{self.getPackageName()}':\n{msg}"
class BinaryPackagePublishingHistory(BaseWrapper): class BinaryPackagePublishingHistory(BaseWrapper):
""" """