New Tool: ubuntu-upload-permission: Query upload permissions (LP: #876554)

This commit is contained in:
Stefano Rivera 2011-12-04 00:53:37 +02:00
commit 89e15a2f5d
7 changed files with 294 additions and 8 deletions

3
debian/changelog vendored
View File

@ -22,10 +22,11 @@ ubuntu-dev-tools (0.137) UNRELEASED; urgency=low
- Do the report boiler-plate checking in a script that wraps an editor, so
that we only edit the report once, after checking for duplicates.
- rm the tmpdir with a little more force (shutil.rmtree) (LP: #899399)
* New Tool: ubuntu-upload-permission: Query upload permissions (LP: #876554)
[ Andreas Moog ]
* sponsor-patch: Check permission to unsubscribe sponsors-team (LP: #896884)
* grep-merges: We already require a UTF-8 enabled terminal, so encode
* grep-merges: We already require a UTF-8 enabled terminal, so encode
package and uploader name in UTF-8 (LP: #694388)
* requestsync: Give user option to retry in case of temporary error (LP: #850360)

3
debian/control vendored
View File

@ -68,6 +68,7 @@ Description: useful tools for Ubuntu developers
.
- 404main - used to check what components a package's deps are in, for
doing a main inclusion report for example.
- backportpackage - helper to test package backports
- bitesize - add the 'bitesize' tag to a bug and comment that you are
willing to help fix it.
- check-mir - check support status of build/binary dependencies
@ -105,4 +106,6 @@ Description: useful tools for Ubuntu developers
- ubuntu-build - give commands to the Launchpad build daemons from the
command line.
- ubuntu-iso - output information of an Ubuntu ISO image.
- ubuntu-upload-permission - query / list the upload permissions for a
package.
- update-maintainer - script to update maintainer field in ubuntu packages.

2
debian/copyright vendored
View File

@ -150,6 +150,7 @@ Files: doc/pull-debian-debdiff.1
doc/reverse-depends.1
doc/sponsor-patch.1
doc/ubuntu-dev-tools.5
doc/ubuntu-upload-permission.1
doc/update-maintainer.1
enforced-editing-wrapper
pull-debian-debdiff
@ -158,6 +159,7 @@ Files: doc/pull-debian-debdiff.1
reverse-depends
sponsor-patch
test-data/*
ubuntu-upload-permission
ubuntutools/archive.py
ubuntutools/builder.py
ubuntutools/config.py

View File

@ -0,0 +1,60 @@
.\" Copyright (C) 2011, Stefano Rivera <stefanor@ubuntu.com>
.\"
.\" Permission to use, copy, modify, and/or distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
.\" copyright notice and this permission notice appear in all copies.
.\"
.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
.\" REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
.\" AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
.\" INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
.\" LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
.\" OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
.\" PERFORMANCE OF THIS SOFTWARE.
.TH ubuntu\-upload\-permission 1 "November 2011" ubuntu\-dev\-tools
.SH NAME
ubuntu\-upload\-permission \- Query upload rights and (optionally) list
the people and teams with upload rights for a package
.SH SYNOPSIS
.B ubuntu\-upload\-permission \fR[\fIoptions\fR] \fIpackage
.SH DESCRIPTION
\fBubuntu\-upload\-permission\fR checks if the user has upload
permissions for \fIpackage\fR.
If the \fB\-\-list\-uploaders\fR option is provided, all the people and
teams that do have upload rights for \fIpackage\fR will be listed.
.SH OPTIONS
.TP
\fB\-r\fR \fIRELEASE\fR, \fB\-\-release\fR=\fIRELEASE\fR
Query permissions in \fIRELEASE\fR.
Default: current development release.
.TP
\fB\-a\fR, \fB\-\-list\-uploaders\fR
List all the people and teams who have upload rights for \fIpackage\fR.
.TP
\fB\-t\fR, \fB\-\-list\-team\-members\fR
List all the members of every team with rights. (Implies
\fB\-\-list\-uploaders\fR)
.TP
\fB\-h\fR, \fB\-\-help\fR
Display a help message and exit
.SH EXIT STATUS
.TP
.B 0
You have the necessary upload rights.
.TP
.B 1
You don't have the necessary upload rights.
.TP
.B 2
There was an error.
.SH AUTHORS
\fBubuntu\-upload\-permission\fR and this manpage were written by
Stefano Rivera <stefanor@ubuntu.com>.
.PP
Both are released under the terms of the ISC License.

View File

@ -41,6 +41,7 @@ scripts = ['404main',
'syncpackage',
'ubuntu-build',
'ubuntu-iso',
'ubuntu-upload-permission',
'update-maintainer',
]

145
ubuntu-upload-permission Executable file
View File

@ -0,0 +1,145 @@
#!/usr/bin/python
#
# Copyright (C) 2011, Stefano Rivera <stefanor@ubuntu.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import optparse
import sys
from devscripts.logger import Logger
from ubuntutools.lp.lpapicache import (Launchpad, Distribution, PersonTeam,
Packageset, PackageNotFoundException,
SeriesNotFoundException)
from ubuntutools.misc import split_release_pocket
def parse_arguments():
'''Parse arguments and return (options, package)'''
parser = optparse.OptionParser('%prog [options] package')
parser.add_option('-r', '--release', default=None, metavar='RELEASE',
help='Use RELEASE, rather than the current development '
'release')
parser.add_option('-a', '--list-uploaders',
default=False, action='store_true',
help='List all the people/teams with upload rights')
parser.add_option('-t', '--list-team-members',
default=False, action='store_true',
help='List all team members of teams with upload rights '
'(implies --list-uploaders)')
options, args = parser.parse_args()
if len(args) != 1:
parser.error("One (and only one) package must be specified")
package = args[0]
if options.list_team_members:
options.list_uploaders = True
return (options, package)
def main():
'''Query upload permissions'''
options, package = parse_arguments()
# Need to be logged in to see uploaders:
Launchpad.login()
ubuntu = Distribution('ubuntu')
archive = ubuntu.getArchive()
if options.release is None:
options.release = ubuntu.getDevelopmentSeries().name
try:
release, pocket = split_release_pocket(options.release)
series = ubuntu.getSeries(release)
except SeriesNotFoundException, e:
Logger.error(str(e))
sys.exit(2)
try:
spph = archive.getSourcePackage(package)
except PackageNotFoundException, e:
Logger.error(str(e))
sys.exit(2)
component = spph.getComponent()
if (options.list_uploaders and (pocket != 'Release' or series.status in
('Experimental', 'Active Development', 'Pre-release Freeze'))):
component_uploader = archive.getUploadersForComponent(
component_name=component)[0]
print "All upload permissions for %s:" % package
print
print "Component (%s)" % component
print "============" + ("=" * len(component))
print_uploaders([component_uploader], options.list_team_members)
packagesets = sorted(Packageset.setsIncludingSource(
distroseries=series,
sourcepackagename=package))
if packagesets:
print
print "Packagesets"
print "==========="
for packageset in packagesets:
print
print "%s:" % packageset.name
print_uploaders(archive.getUploadersForPackageset(
packageset=packageset), options.list_team_members)
ppu_uploaders = archive.getUploadersForPackage(
source_package_name=package)
if ppu_uploaders:
print
print "Per-Package-Uploaders"
print "====================="
print
print_uploaders(ppu_uploaders, options.list_team_members)
print
if PersonTeam.me.canUploadPackage(archive, series, package, component,
pocket):
print "You can upload %s to %s." % (package, options.release)
else:
print ("You can not upload %s to %s, yourself."
% (package, options.release))
if (series.status in ('Current Stable Release', 'Supported', 'Obsolete')
and pocket == 'Release'):
print ("%s is in the '%s' state. "
"You may want to query the %s-proposed pocket."
% (release, series.status, release))
else:
print ("But you can still contribute to it via the sponsorship "
"process: https://wiki.ubuntu.com/SponsorshipProcess")
if not options.list_uploaders:
print ("To see who has the necessary upload rights, "
"use the --list-uploaders option.")
sys.exit(1)
def print_uploaders(uploaders, expand_teams=False, prefix=''):
"""Given a list of uploaders, pretty-print them all
Each line is prefixed with prefix.
If expand_teams is set, recurse, adding more spaces to prefix on each
recursion.
"""
for uploader in sorted(uploaders, key=lambda p: p.display_name):
print ("%s* %s (%s)%s"
% (prefix, uploader.display_name, uploader.name,
' [team]' if uploader.is_team else ''))
if expand_teams and uploader.is_team:
print_uploaders(uploader.participants, True, prefix=prefix + ' ')
if __name__ == '__main__':
main()

View File

@ -54,6 +54,7 @@ __all__ = [
_POCKETS = ('Release', 'Security', 'Updates', 'Proposed', 'Backports')
class _Launchpad(object):
'''Singleton for LP API access.'''
@ -116,7 +117,7 @@ class BaseWrapper(object):
A base class from which other wrapper classes are derived.
'''
__metaclass__ = MetaWrapper
resource_type = None # it's a base class after all
resource_type = None # it's a base class after all
def __new__(cls, data):
if (isinstance(data, basestring) and
@ -278,11 +279,11 @@ class Archive(BaseWrapper):
resource_type = 'archive'
def __init__(self, *args):
# Don't share between different Archives
if '_binpkgs' not in self.__dict__:
self._binpkgs = dict()
if '_srcpkgs' not in self.__dict__:
self._srcpkgs = dict()
self._binpkgs = {}
self._srcpkgs = {}
self._pkg_uploaders = {}
self._pkgset_uploaders = {}
self._component_uploaders = {}
def getSourcePackage(self, name, series=None, pocket=None):
'''
@ -400,6 +401,45 @@ class Archive(BaseWrapper):
include_binaries=include_binaries
)
def getUploadersForComponent(self, component_name):
'''Get the list of PersonTeams who can upload packages in the
specified component.
[Note: the permission records, themselves, aren't exposed]
'''
if component_name not in self._component_uploaders:
self._component_uploaders[component_name] = sorted(set(
PersonTeam(permission.person_link)
for permission in self._lpobject.getUploadersForComponent(
component_name=component_name
)))
return self._component_uploaders[component_name]
def getUploadersForPackage(self, source_package_name):
'''Get the list of PersonTeams who can upload source_package_name)
[Note: the permission records, themselves, aren't exposed]
'''
if source_package_name not in self._pkg_uploaders:
self._pkg_uploaders[source_package_name] = sorted(set(
PersonTeam(permission.person_link)
for permission in self._lpobject.getUploadersForPackage(
source_package_name=source_package_name
)))
return self._pkg_uploaders[source_package_name]
def getUploadersForPackageset(self, packageset, direct_permissions=False):
'''Get the list of PersonTeams who can upload packages in packageset
[Note: the permission records, themselves, aren't exposed]
'''
key = (packageset, direct_permissions)
if key not in self._pkgset_uploaders:
self._pkgset_uploaders[key] = sorted(set(
PersonTeam(permission.person_link)
for permission in self._lpobject.getUploadersForPackageset(
packageset=packageset._lpobject,
direct_permissions=direct_permissions,
)))
return self._pkgset_uploaders[key]
class SourcePackagePublishingHistory(BaseWrapper):
'''
@ -585,6 +625,7 @@ class MetaPersonTeam(MetaWrapper):
raise
return cls._me
class PersonTeam(BaseWrapper):
'''
Wrapper class around a LP person or team object.
@ -687,7 +728,7 @@ class Build(BaseWrapper):
def rescore(self, score):
if self.can_be_rescored:
self().rescore(score = score)
self().rescore(score=score)
return True
return False
@ -703,3 +744,36 @@ class DistributionSourcePackage(BaseWrapper):
Caching class for distribution_source_package objects.
'''
resource_type = 'distribution_source_package'
class Packageset(BaseWrapper):
'''
Caching class for packageset objects.
'''
resource_type = 'packageset'
_lp_packagesets = None
_source_sets = {}
@classmethod
def setsIncludingSource(cls, sourcepackagename, distroseries=None,
direct_inclusion=False):
'''Get the package sets including sourcepackagename'''
if cls._lp_packagesets is None:
cls._lp_packagesets = Launchpad.packagesets
key = (sourcepackagename, distroseries, direct_inclusion)
if key not in cls._source_sets:
params = {
'sourcepackagename': sourcepackagename,
'direct_inclusion': direct_inclusion,
}
if distroseries is not None:
params['distroseries'] = distroseries._lpobject
cls._source_sets[key] = [
Packageset(packageset) for packageset
in cls._lp_packagesets.setsIncludingSource(**params)
]
return cls._source_sets[key]