Merge from trunk

This commit is contained in:
Stefano Rivera 2011-12-08 00:03:37 +02:00
commit 8e341da95c
8 changed files with 349 additions and 18 deletions

8
debian/changelog vendored
View File

@ -22,11 +22,17 @@ 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 Tools: (LP: #876554)
- ubuntu-upload-permission: Query upload permissions.
- ubuntu-is-seeded: Query a package's seed status. Whether it is on
current daily images and/or part of the supported seed.
[ 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)
-- Andreas Moog <amoog@ubuntu.com> Wed, 30 Nov 2011 21:04:39 +0100

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
@ -106,4 +107,6 @@ Description: useful tools for Ubuntu developers
command line.
- ubuntu-is-seeded - query if a package is safe to upload during a freeze.
- 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

@ -151,6 +151,7 @@ Files: doc/pull-debian-debdiff.1
doc/sponsor-patch.1
doc/ubuntu-dev-tools.5
doc/ubuntu-is-seeded.1
doc/ubuntu-upload-permission.1
doc/update-maintainer.1
enforced-editing-wrapper
pull-debian-debdiff
@ -160,6 +161,7 @@ Files: doc/pull-debian-debdiff.1
sponsor-patch
test-data/*
ubuntu-is-seeded
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

@ -42,6 +42,7 @@ scripts = ['404main',
'ubuntu-build',
'ubuntu-is-seeded',
'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]

View File

@ -21,9 +21,11 @@
# of the GNU General Public License license.
import os
import re
import sys
import smtplib
import socket
import tempfile
from debian.changelog import Changelog, Version
from devscripts.logger import Logger
@ -162,14 +164,33 @@ Content-Type: text/plain; charset=UTF-8
print 'The final report is:\n%s' % mail
confirmation_prompt()
# save mail in temporary file
backup = tempfile.NamedTemporaryFile(mode='w', delete=False,
prefix='requestsync-' + re.sub(r'[^a-zA-Z0-9_-]', '',
bugtitle.replace(' ', '_')))
with backup:
backup.write(mail)
Logger.normal('The e-mail has been saved in %s and will be deleted '
'after succesful transmission', backup.name)
# connect to the server
try:
Logger.info('Connecting to %s:%s ...', mailserver_host, mailserver_port)
s = smtplib.SMTP(mailserver_host, mailserver_port)
except socket.error, s:
Logger.error('Could not connect to %s:%s: %s (%i)',
mailserver_host, mailserver_port, s[1], s[0])
return
while True:
try:
Logger.normal('Connecting to %s:%s ...', mailserver_host,
mailserver_port)
s = smtplib.SMTP(mailserver_host, mailserver_port)
break
except socket.error, s:
Logger.error('Could not connect to %s:%s: %s (%i)',
mailserver_host, mailserver_port, s[1], s[0])
return
except smtplib.SMTPConnectError, s:
Logger.error('Could not connect to %s:%s: %s (%i)',
mailserver_host, mailserver_port, s[1], s[0])
if s.smtp_code == 421:
confirmation_prompt(message='This is a temporary error, press '
'[Enter] to retry. Press [Ctrl-C] to abort now.')
if mailserver_user and mailserver_pass:
try:
@ -184,6 +205,25 @@ Content-Type: text/plain; charset=UTF-8
s.quit()
return
s.sendmail(myemailaddr, to, mail.encode('utf-8'))
s.quit()
Logger.normal('Sync request mailed.')
while True:
try:
s.sendmail(myemailaddr, to, mail.encode('utf-8'))
s.quit()
os.remove(backup.name)
Logger.normal('Sync request mailed.')
break
except smtplib.SMTPRecipientsRefused, smtperror:
smtp_code, smtp_message = smtperror.recipients[to]
Logger.error('Error while sending: %i, %s', smtp_code, smtp_message)
if smtp_code == 450:
confirmation_prompt(message='This is a temporary error, press '
'[Enter] to retry. Press [Ctrl-C] to abort now.')
else:
return
except smtplib.SMTPResponseException, e:
Logger.error('Error while sending: %i, %s',
e.smtp_code, e.smtp_error)
return
except smtplib.SMTPServerDisconnected:
Logger.error('Server disconnected while sending the mail.')
return