Add a -A archive option to act on ppas as well.

This results in a major refactor of the code to use launchpadlib directly
instead of the ubuntutools.lp.lpapicache module in ubuntu-dev-tools which is
idiosyncratic and does not expose the full launchpad API.  Easier to rewrite
to use the standard library.
This commit is contained in:
Steve Langasek 2024-03-10 14:35:47 -07:00
parent 0bef4d7352
commit 010af53d7c
2 changed files with 142 additions and 115 deletions

View File

@ -29,19 +29,52 @@ 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 build.rescore(score):
res.append(f" {arch}: done")
else:
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"
@ -74,6 +107,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",
@ -102,7 +138,7 @@ def main():
) )
batch_options.add_argument( batch_options.add_argument(
"--state", action="store", dest="state", "--state", action="store", dest="state",
help="Act on builds that are in the specified state (default: Failed to build)", help="Act on builds that are in the specified state",
) )
parser.add_argument("packages", metavar="package", nargs="*", help=argparse.SUPPRESS) parser.add_argument("packages", metavar="package", nargs="*", help=argparse.SUPPRESS)
@ -110,14 +146,25 @@ def main():
# 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: is_buildd_admin = any(t.name == "launchpad-buildd-admins" \
for t in me.super_teams)
ubuntu = launchpad.distributions['ubuntu']
if args.batch:
release = args.series
if not release:
release = ubuntu.getDevelopmentSeries().name + "-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.")
@ -130,6 +177,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)
@ -153,33 +208,35 @@ 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: pocket=pocket,
Logger.error(error) 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": if operation == "rescore":
necessary_privs = me.isLpTeamMember("launchpad-buildd-admins") necessary_privs = is_buildd_admin
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 in ("rescore", "retry") and not necessary_privs:
@ -245,24 +302,8 @@ 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 release:
release = Distribution("ubuntu").getDevelopmentSeries().name + "-proposed"
try:
(release, pocket) = split_release_pocket(release)
except PocketDoesNotExistError as error:
Logger.error(error)
sys.exit(1)
ubuntu_archive = Distribution("ubuntu").getArchive()
try:
distroseries = Distribution("ubuntu").getSeries(release)
except SeriesNotFoundException as error:
Logger.error(error)
sys.exit(1)
# Check permisions (part 1): Rescoring can only be done by buildd admins # Check permisions (part 1): Rescoring can only be done by buildd admins
can_rescore = args.priority and me.isLpTeamMember("launchpad-buildd-admins") can_rescore = args.priority and is_buildd_admin
if args.priority and not can_rescore: if args.priority and not can_rescore:
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."
@ -272,11 +313,27 @@ def main():
retry_count = 0 retry_count = 0
if not args.state: if not args.state:
if args.retry:
args.state='Failed to build' args.state='Failed to build'
series = Distribution("ubuntu").getSeries(release) elif args.priority:
for build in series.getBuildRecords( args.state='Needs building'
build_state=args.state, pocket=pocket # 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: if build.arch_tag not in archs:
continue continue
if not build.current_source_publication: if not build.current_source_publication:
@ -284,9 +341,12 @@ def main():
# fixme: refactor # fixme: refactor
# Check permissions (part 2): check upload permissions for the # Check permissions (part 2): check upload permissions for the
# source package # source package
can_retry = args.retry and me.canUploadPackage( can_retry = args.retry and archive.checkUpload(
ubuntu_archive, series, build.source_package_name, component=build.current_source_publication.component_name,
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: if args.retry and not can_retry:
Logger.error( Logger.error(
@ -303,57 +363,69 @@ def main():
build.source_package_version build.source_package_version
) )
if can_retry: if args.retry:
Logger.info("Retrying build of %s on %s...", Logger.info("Retrying build of %s on %s...",
build.source_package_name, build.arch_tag) build.source_package_name, build.arch_tag)
retry_count += 1 retry_count += 1
build.retry() build.retry()
else:
Logger.info("Cannot retry build of %s on %s.",
build.source_package_name, build.arch_tag)
if args.priority and can_rescore: if args.priority and can_rescore:
Logger.info(pkg.rescoreBuilds(archs, args.priority)) if build.can_be_rescored:
build.rescore(score=args.priority)
else:
Logger.info("Cannot rescore build of %s on %s.",
build.source_package_name, build.arch_tag)
Logger.info("") Logger.info("")
if args.retry:
Logger.info("%d package builds retried", retry_count) Logger.info("%d package builds retried", retry_count)
sys.exit(0) 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) 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 and can_rescore:
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):
""" """