This commit is contained in:
Iain Lane 2009-01-18 17:46:31 +00:00
commit 0e291144a4
15 changed files with 579 additions and 156 deletions

3
buildd
View File

@ -31,8 +31,7 @@ from optparse import OptionParser
from urllib import urlencode from urllib import urlencode
# ubuntu-dev-tools modules. # ubuntu-dev-tools modules.
sys.path.append("/usr/share/ubuntu-dev-tools/") from ubuntutools import common
import common
# Usage. # Usage.
usage = "%prog <srcpackage> <release> <operation>\n\n" usage = "%prog <srcpackage> <release> <operation>\n\n"

79
debian/changelog vendored
View File

@ -1,4 +1,64 @@
ubuntu-dev-tools (0.52) UNRELEASED; urgency=low ubuntu-dev-tools (0.58) UNRELEASED; urgency=low
* Changes go here.
-- Jonathan Davies <jpds@ubuntu.com> Sat, 17 Jan 2009 21:04:55 +0000
ubuntu-dev-tools (0.57) jaunty; urgency=low
* requestsync: Skip existing bug check if no credentials are
found (LP: #318120).
-- Jonathan Davies <jpds@ubuntu.com> Sat, 17 Jan 2009 22:02:39 +0000
ubuntu-dev-tools (0.56) jaunty; urgency=low
* manage-credentials: Tighted security by making credentials files and
folder world unreadable.
* common.py: Improved no credentials found error message to show which
consumer token is needed.
* requestsync: Catch credentials error to hide traceback.
* Moved common.py to ubuntutools/ subdirectory to avoid possible conflicts
in Python packaging and fixed all imports as necessary.
* debian/ubuntu-dev-tools.install: Removed common.py entry.
-- Jonathan Davies <jpds@ubuntu.com> Sat, 17 Jan 2009 11:32:33 +0000
ubuntu-dev-tools (0.55) jaunty; urgency=low
* manage-credentials: Use common.py's mkdir function to create as many
subdirectories as necessary for the credentials directory (LP: #317317).
-- Jonathan Davies <jpds@ubuntu.com> Thu, 15 Jan 2009 12:33:31 +0000
ubuntu-dev-tools (0.54) jaunty; urgency=low
* manage-credentials:
- Save credentials to ~/.cache/lp_credentials/ by
default.
- Set service option default to edge.
* doc/manage-credentials.1: Update as necessary for the above.
* common.py:
- When credentials are not found, ask user to see
manage-credentials manpage.
- Load all token files for the consumer specified in the above
directory as necessary.
-- Jonathan Davies <jpds@ubuntu.com> Wed, 14 Jan 2009 19:39:35 +0000
ubuntu-dev-tools (0.53) jaunty; urgency=low
[ Siegfried-Angel Gevatter Pujals ]
* debian/copyright:
- Add information about manage-credentials.
[ Daniel Holbach ]
* debian/control: replace 'sb-release' with lsb-release, make package
installable again.
-- Daniel Holbach <daniel.holbach@ubuntu.com> Wed, 14 Jan 2009 16:27:34 +0100
ubuntu-dev-tools (0.52) jaunty; urgency=low
[ Siegfried-Angel Gevatter Pujals ] [ Siegfried-Angel Gevatter Pujals ]
* pbuilder-dist.new: * pbuilder-dist.new:
@ -32,8 +92,23 @@ ubuntu-dev-tools (0.52) UNRELEASED; urgency=low
'status'. 'status'.
* requestsync: If package is new, check the Ubuntu Archive team's bug list * requestsync: If package is new, check the Ubuntu Archive team's bug list
for possible duplicate requests. for possible duplicate requests.
* doc/manage-credentials.1: Written up.
* doc/requestsync.1: Changed documentation to launchpadlib related-stuff.
-- Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com> Sat, 10 Jan 2009 14:35:14 +0100 [ Luca Falavigna ]
* requestsync:
- Catch AssertionError exception if rmadison returns with an error.
[ Markus Korn ]
* Added manage-credentials, a tool to create (and manage) credentials
which are used to access launchpad via the API.
* Ported: hugdaylist, massfile, grab-attachment and requestsync to
launchpadlib.
* Other misc. fixes and tweaks.
* Install common.py to correct location with py_modules and remove
hardcoded path from files.
-- Jonathan Davies <jpds@ubuntu.com> Wed, 14 Jan 2009 13:21:35 +0000
ubuntu-dev-tools (0.51) jaunty; urgency=low ubuntu-dev-tools (0.51) jaunty; urgency=low

4
debian/control vendored
View File

@ -14,8 +14,8 @@ Package: ubuntu-dev-tools
Architecture: all Architecture: all
Section: devel Section: devel
Depends: ${python:Depends}, binutils, devscripts, sudo, python-debian, Depends: ${python:Depends}, binutils, devscripts, sudo, python-debian,
python-launchpad-bugs (>= 0.2.25), dctrl-tools, lsb-release, diffstat, python-launchpad-bugs (>= 0.2.25), python-launchpadlib, dctrl-tools,
dpkg-dev, ${misc:Depends} lsb-release, diffstat, dpkg-dev, ${misc:Depends}
Recommends: bzr, pbuilder | cowdancer | sbuild, reportbug (>= 3.39ubuntu1), Recommends: bzr, pbuilder | cowdancer | sbuild, reportbug (>= 3.39ubuntu1),
ca-certificates, genisoimage, perl-modules, libwww-perl ca-certificates, genisoimage, perl-modules, libwww-perl
Conflicts: devscripts (<< 2.10.7ubuntu5) Conflicts: devscripts (<< 2.10.7ubuntu5)

18
debian/copyright vendored
View File

@ -13,6 +13,7 @@ Upstream Authors:
Jordan Mantha <mantha@ubuntu.com> Jordan Mantha <mantha@ubuntu.com>
Kees Cook <kees@ubuntu.com> Kees Cook <kees@ubuntu.com>
Luke Yelavich <themuso@ubuntu.com> Luke Yelavich <themuso@ubuntu.com>
Markus Korn <thekorn@gmx.de>
Martin Pitt <martin.pitt@ubuntu.com> Martin Pitt <martin.pitt@ubuntu.com>
Matt Zimmerman <mdz@ubuntu.com> Matt Zimmerman <mdz@ubuntu.com>
Michael Bienia <geser@ubuntu.com> Michael Bienia <geser@ubuntu.com>
@ -30,14 +31,15 @@ Copyright:
(C) 2006-2007, Albin Tonnerre <lut1n.tne@gmail.com> (C) 2006-2007, Albin Tonnerre <lut1n.tne@gmail.com>
(C) 2006-2007, Daniel Holbach <daniel.holbach@ubuntu.com> (C) 2006-2007, Daniel Holbach <daniel.holbach@ubuntu.com>
(C) 2006-2007, Luke Yelavich <themuso@ubuntu.com> (C) 2006-2007, Luke Yelavich <themuso@ubuntu.com>
(C) 2009, Markus Korn <thekorn@gmx.de>
(C) 2007, Martin Pitt <martin.pitt@ubuntu.com> (C) 2007, Martin Pitt <martin.pitt@ubuntu.com>
(C) 2006-2007, Michael Bienia <geser@ubuntu.com> (C) 2006-2007, Michael Bienia <geser@ubuntu.com>
(C) 2006-2008, Kees Cook <kees@ubuntu.com> (C) 2006-2008, Kees Cook <kees@ubuntu.com>
(C) 2006-2007, Pete Savage <petesavage@ubuntu.com> (C) 2006-2007, Pete Savage <petesavage@ubuntu.com>
(C) 2007-2008, Siegfried-A. Gevatter <rainct@ubuntu.com> (C) 2007-2009, Siegfried-A. Gevatter <rainct@ubuntu.com>
(C) 2007, Terence Simpson <stdin@stdin.me.uk> (C) 2007, Terence Simpson <stdin@stdin.me.uk>
(C) 2008, Iain Lane <iain@orangesquash.org.uk> (C) 2008, Iain Lane <iain@orangesquash.org.uk>
(C) 2008, Jonathan Davies <jpds@ubuntu.com> (C) 2008-2009, Jonathan Davies <jpds@ubuntu.com>
(C) 2008, Nathan handler <nhandler@ubuntu.com> (C) 2008, Nathan handler <nhandler@ubuntu.com>
Licenses: Licenses:
@ -58,9 +60,10 @@ licensed under the GNU General Public License, version 2:
On Debian and Ubuntu systems, the complete text of the GNU General Public On Debian and Ubuntu systems, the complete text of the GNU General Public
License v2 can be found in `/usr/share/common-licenses/GPL-2'. License v2 can be found in `/usr/share/common-licenses/GPL-2'.
dch-repeat, get-branches, get-build-deps, massfile, mk-sbuild-lv, ppaput, dch-repeat, get-branches, get-build-deps, manage-credentials, massfile,
pull-debian-debdiff, pull-debian-source, pull-lp-source, suspicious-source and what-patch are mk-sbuild-lv, ppaput, pull-debian-debdiff, pull-debian-source, pull-lp-source,
licensed under the GNU General Public License, version 3: suspicious-source and what-patch are licensed under the GNU General Public
License, version 3:
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
@ -76,5 +79,6 @@ License v3 can be found in `/usr/share/common-licenses/GPL-3'.
The following of the scripts can be used, at your option, regarding any The following of the scripts can be used, at your option, regarding any
later version of the previously specified license: 404main, dch-repeat, dgetlp, later version of the previously specified license: 404main, dch-repeat, dgetlp,
get-build-deps, mk-sbuild-lv, pull-debian-debdiff, pull-debian-source, pull-lp-source, get-build-deps, manage-credentials, mk-sbuild-lv, pull-debian-debdiff,
reverse-build-depends, suspicious-source, what-patch. pull-debian-source, pull-lp-source, reverse-build-depends, suspicious-source,
what-patch.

View File

@ -1,2 +1 @@
bash_completion/* etc/bash_completion.d/ bash_completion/* etc/bash_completion.d/
common.py usr/share/ubuntu-dev-tools/

76
doc/manage-credentials.1 Normal file
View File

@ -0,0 +1,76 @@
.TH MANAGE-CREDENTIALS "1" "13 January 2009" "ubuntu-dev-tools"
.SH NAME
manage-credentials \- a tool to create (and manage) credentials which
are used to access launchpad via the API.
.SH SYNOPSIS
.B manage-credentials create -c <consumer> [--email <email> --password <password>] [--service <staging|edge>]
.br
.B manage-credentials \-h
.SH DESCRIPTION
\fBmanage-credentials\fR is a tool to create (and manage) credentials which
are used to access Launchpad via the API.
.PP
Currently this tool can be used
to create a token with or without using the web UI. In the future, once
related methods are available through the API, this tool can also be used
to manage tokens in launchpad and on the users local machine.
.SH OPTIONS
Listed below are the command line options for requestsync:
.TP
.B \-h
Display a help message and exit.
.TP
.B \-c \-\-consumer
.TP
.B \-e \-\-email <email>
Your email address as registered on Launchpad.
.TP
.B \-p \-\-password <password>
Your Launchpad password.
.TP
.B \-s \-\-service <edge|staging>
If we should use the edge or staging root of the Launchpad API.
.TP
.B \-\-cache
Where to store the cache.
.TP
.B \-o
Which file we should save the credentials to. By default
\fBmanage-credentials\fR writes the credentials tokens to the
~/.cache/lp_credentials/ directory.
.TP
.B \-l \-\-level <number>
A number representing the access-level you wish to give to the new
Launchpad token. 0 is unauthorized, 1 is read public data, 2; write public data,
3; read private data and 4; write private data.
.SH EXAMPLE USAGE
There are currently two ways of using \fBmanage-credentials\fR to get
Launchpad tokens.
.TP
1) manage-credentials create -c CONSUMER --level 2
.TP
This way shall open your webbrowser with a Launchpad login page.
.TP
2) manage-credentials create -c CONSUMER --level 2 --password BOO --email me@example.com
.TP
This is a hack, but it works and does not require a webbrowser .
.TP
If you intend to use manage-credentials for Ubuntu development (such as
the ubuntu-dev-tools package). Please by sure to run the following:
.TP
manage-credentials create -c ubuntu-dev-tools -l 2
.SH AUTHOR
.B manage-credentials
was written by Markus Korn <thekorn@gmx.de> and this manual page was written by
Jonathan Davies <jpds@ubuntu.com>.
.PP
Both are released under the GNU General Public License, version 3.

View File

@ -15,10 +15,10 @@ The changelog entry is then downloaded from packages.debian.org.
If the sync request is being filed per email (default), a prompt for your If the sync request is being filed per email (default), a prompt for your
GPG passphrase follows so that it can sign the mail and send it off to GPG passphrase follows so that it can sign the mail and send it off to
Launchpad. Launchpad.
Alternatively a sync request can be filed directly using the launchpadbugs Alternatively a sync request can be filed directly using the launchpadlib
python module (option \fB\-\-lp\fR). Python module (option \fB\-\-lp\fR).
\fBrequestsync\fR falls back to mail the sync request if submitting using \fBrequestsync\fR falls back to mail the sync request if submitting using
the launchpadbugs module fails. the launchpadlib module fails.
.PP .PP
\fBrequestsync\fR checks if you have the permissions to request the sync from \fBrequestsync\fR checks if you have the permissions to request the sync from
@ -29,10 +29,8 @@ If you are not a member of the appropriate team, the script will subscribe
the necessary team with approval rights to the bug report for you. the necessary team with approval rights to the bug report for you.
.PP .PP
\fBrequestsync\fR uses a cookie file stored at \fI~/.lpcookie.txt\fR to \fBrequestsync\fR uses launchpadlib authentication to file its requests. Please
authenticate with Launchpad. see manage-credentials(1) for more information.
This cookie is created on run from the Mozilla Firefox cookie file at
\fI~/.mozilla/*/*/cookies.sqlite\fR.
.SH OPTIONS .SH OPTIONS
Listed below are the command line options for requestsync: Listed below are the command line options for requestsync:
@ -55,7 +53,7 @@ configuration (for example: \fI$HOME/.bashrc\fR).
This is only used if the sync request is mailed to Launchpad. This is only used if the sync request is mailed to Launchpad.
.TP .TP
.B \-\-lp .B \-\-lp
Use the launchpadbugs python module (packaged as python\-launchpad\-bugs) to Use the launchpadlib Python module (packaged as python\-launchpadlib) to
file the sync request in Launchpad. file the sync request in Launchpad.
.TP .TP
.B \-s .B \-s

View File

@ -20,8 +20,7 @@
import os import os
import sys import sys
import urllib from ubuntutools.common import get_launchpad
import launchpadbugs.connector as Connector
USAGE = "grab-attachments <bug numbers>" USAGE = "grab-attachments <bug numbers>"
@ -33,17 +32,21 @@ def main():
if sys.argv[1] in ["--help", "-h"]: if sys.argv[1] in ["--help", "-h"]:
print USAGE print USAGE
sys.exit(0) sys.exit(0)
Bug = Connector.ConnectBug(method="Text") launchpad = get_launchpad("ubuntu-dev-tools")
for arg in sys.argv[1:]: for arg in sys.argv[1:]:
try: try:
number = int(arg) number = int(arg)
except: except:
print >> sys.stderr, "'%s' is not a valid bug number." % arg print >> sys.stderr, "'%s' is not a valid bug number." % arg
sys.exit(1) sys.exit(1)
b = Bug(number) b = launchpad.bugs[number]
for a in b.attachments: for a in b.attachments:
filename = os.path.join(os.getcwd(), a.url.split("/")[-1]) f = a.data.open()
urllib.urlretrieve(a.url, filename) filename = os.path.join(os.getcwd(), f.filename)
local_file = open(filename, "w")
local_file.write(f.read())
f.close()
local_file.close()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -35,14 +35,7 @@ import string
import sys import sys
from optparse import OptionParser from optparse import OptionParser
try: from ubuntutools.common import get_launchpad, translate_web_api, translate_api_web
import launchpadbugs.connector as Connector
BugList = Connector.ConnectBugList()
Bug = Connector.ConnectBug(method="Text")
except ImportError:
print >> sys.stderr, \
"python-launchpad-bugs (>= 0.2.25) needs to be installed to use hugdaylist."
sys.exit(1)
def check_args(): def check_args():
howmany = -1 howmany = -1
@ -74,26 +67,40 @@ def check_args():
return (howmany, url) return (howmany, url)
def filter_unsolved(b): def filter_unsolved(task):
bug = Bug(int(b)) # TODO: don't use this filter here, only check status and assignee of
# the given task
# Filter out special types of bugs: # Filter out special types of bugs:
# - https://wiki.ubuntu.com/Bugs/HowToTriage#Special%20types%20of%20bugs # - https://wiki.ubuntu.com/Bugs/HowToTriage#Special%20types%20of%20bugs
return filter(lambda a: a.status != 'Fix Committed' and \ subscriptions = set(s.person.name for s in task.bug.subscriptions) #this is expensive, parse name out of self_link instead?
(a.assignee in ['motu','desktop-bugs'] or \ if (task.status != "Fix Committed" and
not a.assignee), bug.infotable) and \ (not task.assignee or task.assignee.name in ['motu','desktop-bugs']) and
'ubuntu-main-sponsors' not in [str(s) for s in bug.subscribers] and \ 'ubuntu-main-sponsors' not in subscriptions and
'ubuntu-universe-sponsors' not in [str(s) for s in bug.subscribers] and \ 'ubuntu-universe-sponsors' not in subscriptions and
'ubuntu-archive' not in [str(s) for s in bug.subscribers] 'ubuntu-archive' not in subscriptions):
return True
return False
def main(): def main():
(howmany, url) = check_args() (howmany, url) = check_args()
if len(url.split("?", 1)) == 2:
try: # search options not supported, because there is no mapping web ui options <-> API options
bl = BugList(url) print >> sys.stderr, "Options in url are not supported, url: %s" %url
except:
print >> sys.stderr, "The URL at '%s' does not appear to have a bug " \
"list." % url
sys.exit(1) sys.exit(1)
launchpad = get_launchpad("ubuntu-dev-tools")
api_url = translate_web_api(url, launchpad)
try:
product = launchpad.load(api_url)
except Exception, e:
x = getattr(e, "response", {})
if response.get("status", None) == "404":
print >> sys.stderr, "The URL at '%s' does not appear to be a valid url to a product" %url
sys.exit(1)
else:
raise
bl = product.searchTasks()
l = filter(filter_unsolved, bl) l = filter(filter_unsolved, bl)
@ -112,13 +119,14 @@ def main():
|| Bug || Subject || Triager ||""" || Bug || Subject || Triager ||"""
for i in list(l)[:howmany]: for i in list(l)[:howmany]:
bug = i.bug
print '||<rowbgcolor="#FFEBBB"> [%s %s] || %s || ||' % \ print '||<rowbgcolor="#FFEBBB"> [%s %s] || %s || ||' % \
(i.url, i.bugnumber, i.summary) (translate_api_web(bug.self_link), bug.id, bug.title)
if __name__ == '__main__': if __name__ == '__main__':
try: try:
main() main()
except KeyboardInterrupt: except KeyboardInterrupt:
print >> sys.stderr, "Aborted." print >> sys.stderr, "Aborted."
sys.exit(1) sys.exit(1)

137
manage-credentials Executable file
View File

@ -0,0 +1,137 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Markus Korn <thekorn@gmx.de>
#
# ##################################################################
#
# 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; either version 3
# of the License, or (at your option) any later version.
#
# 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.
#
# See file /usr/share/common-licenses/GPL for more details.
#
# ##################################################################
import os
import sys
from optparse import OptionParser, make_option
from ubuntutools.common import Credentials, Launchpad, translate_service
from ubuntutools.common import LEVEL, translate_api_web, approve_application
from ubuntutools.common import mkdir
class CmdOptions(OptionParser):
USAGE = (
"\t%prog create -c <consumer> [--email <email> --password <password>] [--service <staging|edge>]\n"
)
OPTIONS = (
make_option("-c", "--consumer", action="store", type="string",
dest="consumer", default=None),
make_option("-e", "--email", action="store", type="string",
dest="email", default=None),
make_option("-p", "--password", action="store", type="string",
dest="password", default=None),
make_option("-s", "--service", action="store", type="string",
dest="service", default="edge"),
make_option("--cache", action="store", type="string",
dest="cache", default=None),
make_option("-o", action="store", type="string",
dest="output", default=None),
make_option("-l", "--level", action="store", type="int",
dest="level", default=0,
help="integer representing the access-level (default: 0), mapping: %s" %LEVEL),
)
TOOLS = {
"create": ( ("consumer",),
("email", "password", "service", "cache", "output",
"level")),
"list": (tuple(), ("service", )),
}
def __init__(self):
OptionParser.__init__(self, option_list=self.OPTIONS)
self.set_usage(self.USAGE)
def parse_args(self, args=None, values=None):
options, args = OptionParser.parse_args(self, args, values)
given_options = set(i for i, k in self.defaults.iteritems() if not getattr(options, i) == k)
if not args:
self.error("Please define a sub-tool you would like to use")
if not len(args) == 1:
self.error("Only one sub-tool allowed")
else:
tool = args.pop()
if not tool in self.TOOLS:
self.error("Unknown tool '%s'" %tool)
needed_options = set(self.TOOLS[tool][0]) - given_options
if needed_options:
self.error("Please define the following options: %s" %", ".join(needed_options))
optional_options = given_options - set(sum(self.TOOLS[tool], ()))
if optional_options:
self.error("The following options are not allowed for this tool: %s" %", ".join(optional_options))
options.service = translate_service(options.service)
if options.level in LEVEL:
options.level = LEVEL[options.level]
elif options.level.upper() in LEVEL.values():
options.level = options.level.upper()
else:
self.error("Unknown access-level '%s', level must be in %s" %(options.level, self.LEVEL))
return tool, options
def create_credentials(options):
if options.password and options.email:
# use hack
credentials = Credentials(options.consumer)
credentials = approve_application(credentials, options.email,
options.password, options.level,
translate_api_web(options.service), None)
else:
launchpad = Launchpad.get_token_and_login(options.consumer,
options.service, options.cache)
credentials = launchpad.credentials
if options.output:
filepath = options.output
else:
credentialsDir = os.path.expanduser("~/.cache/lp_credentials")
if not os.path.isdir(credentialsDir):
mkdir(credentialsDir)
os.chmod(credentialsDir, 0700)
filepath = os.path.expanduser("%s/%s-%s.txt" % \
(credentialsDir, options.consumer, str(options.level).lower()))
f = open(filepath, "w")
# Make credentials file non-world readable.
os.chmod(filepath, 0600)
credentials.save(f)
f.close()
print "Credentials sucessfully written to %s." % filepath
return
def list_tokens(options):
print "Not implemented yet."
print "To get a list of your tokens, please visit %speople/+me/+oauth-tokens" %translate_api_web(options.service)
return 1
def main():
cmdoptions = CmdOptions()
tool, options = cmdoptions.parse_args()
if tool == "create":
return create_credentials(options)
elif tool == "list":
return list_tokens(options)
if __name__ == "__main__":
sys.exit(main())

View File

@ -27,12 +27,7 @@ import email
import subprocess import subprocess
import glob import glob
import launchpadbugs.connector as Connector from ubuntutools.common import get_launchpad, translate_api_web, translate_web_api
sys.path.append('/usr/share/ubuntu-dev-tools/')
import common
cookie = common.prepareLaunchpadCookie()
def read_config(): def read_config():
instructions_file = open("instructions") instructions_file = open("instructions")
@ -57,10 +52,6 @@ def read_list():
listfile.close() listfile.close()
return pack_list return pack_list
def file_bug():
Bug = Connector.ConnectBug()
Bug.authentication = cookie
def check_configfiles(): def check_configfiles():
result = True result = True
@ -87,43 +78,62 @@ def check_configfiles():
def file_bug(config): def file_bug(config):
Bug = Connector.ConnectBug() launchpad = get_launchpad("ubuntu-dev-tools")
Bug.authentication = cookie
try: try:
summary = config["subject"].replace("$pack", config["sourcepackage"]) summary = config["subject"].replace("$pack", config["sourcepackage"])
description = config["text"].replace("$pack", config["sourcepackage"]) description = config["text"].replace("$pack", config["sourcepackage"])
bug = Bug.New(product={"name": config["sourcepackage"],
"target": "ubuntu"}, product_url = "%subuntu/+source/%s" %(launchpad._root_uri, config["sourcepackage"])
summary=summary, tags = filter(None, map(lambda t: t.strip("\n").strip(), config["tags"].split(",")))
description=description) bug = launchpad.bugs.createBug(description=description, title=summary,
print "Successfully filed bug %s: https://launchpad.net/bugs/%s" % \ target=product_url, tags=tags)
(bug.bugnumber, bug.bugnumber)
for sub in config["subscribers"].split(","): print "Successfully filed bug %i: %s" %(bug.id, translate_api_web(bug.self_link))
if sub.strip("\n").strip():
bug.subscribers.add(sub.strip("\n").strip()) subscribers = filter(None, map(lambda t: t.strip("\n").strip(), config["subscribers"].split(",")))
for tag in config["tags"].split(","): for sub in subscribers:
if tag.strip("\n").strip(): subscribe_url = "%s~%s" %(launchpad._root_uri, sub)
bug.tags.append(tag.strip("\n").strip()) bug.subscribe(person=subscribe_url)
bug.assignee = config["assignee"]
#newly created bugreports have one task
task = bug.bug_tasks[0]
if config["status"]: if config["status"]:
bug.status = config["status"].capitalize() status = config["status"].capitalize()
else: else:
bug.status = "Confirmed" status = "Confirmed"
bug.commit() task.transitionToStatus(status=status)
assignee = config["assignee"]
if assignee:
assignee_url = "%s~%s" %(launchpad._root_uri, assignee)
bug.transitionToAssignee(assignee=assignee_url)
except: except:
"Bug for '%s' was not filed." % config["sourcepackage"] "Bug for '%s' was not filed." % config["sourcepackage"]
def read_buglist(url): def read_buglist(url):
BugList = Connector.ConnectBugList() if not url:
return set()
if len(url.split("?", 1)) == 2:
# search options not supported, because there is no mapping web ui options <-> API options
print >> sys.stderr, "Options in url are not supported, url: %s" %url
sys.exit(1)
launchpad = get_launchpad("ubuntu-dev-tools")
packages = set() packages = set()
if url: api_url = translate_web_api(url, launchpad)
buglist = BugList(url) # workaround LP #303414
for bug in buglist.bugs: # if this is fixed it should simply be: buglist = launchpad.load(api_url)
packages.add(bug.sourcepackage) api_url = api_url.split("?", 1)[0]
project = launchpad.load(api_url)
buglist = project.searchTasks()
for bug in buglist:
packages.add(bug.bug_target_name)
return packages return packages

View File

@ -33,8 +33,7 @@ import urllib2
from optparse import OptionParser from optparse import OptionParser
# Ubuntu-dev-tools modules. # Ubuntu-dev-tools modules.
sys.path.append("/usr/share/ubuntu-dev-tools") from ubuntutools import common
import common
class BackportFromLP: class BackportFromLP:

View File

@ -9,6 +9,7 @@
# Daniel Hahler <ubuntu@thequod.de> # Daniel Hahler <ubuntu@thequod.de>
# Iain Lane <iain@orangesquash.org.uk> # Iain Lane <iain@orangesquash.org.uk>
# Jonathan Davies <jpds@ubuntu.com> # Jonathan Davies <jpds@ubuntu.com>
# Markus Korn <thekorn@gmx.de> (python-launchpadlib support)
# #
# ################################################################## # ##################################################################
# #
@ -34,9 +35,7 @@ from debian_bundle.changelog import Version
from optparse import OptionParser from optparse import OptionParser
from time import sleep from time import sleep
# Use functions from ubuntu-dev-tools to create Launchpad cookie file. from ubuntutools import common
sys.path.append('/usr/share/ubuntu-dev-tools/')
import common
launchpad_cookiefile = common.prepareLaunchpadCookie() launchpad_cookiefile = common.prepareLaunchpadCookie()
@ -54,6 +53,11 @@ def checkNeedsSponsorship(component):
The prepareLaunchpadCookie function above shall ensure that a cookie The prepareLaunchpadCookie function above shall ensure that a cookie
file exists first. file exists first.
""" """
# TODO: use launchpadlib here
# Once LP: #313233 has been fixed this can be implemented by either:
# >>> me = launchpad.me
# >>> me.inTeam(<TEAM>) #or
# >>> me in <TEAM>
urlopener = common.setupLaunchpadUrlOpener(launchpad_cookiefile) urlopener = common.setupLaunchpadUrlOpener(launchpad_cookiefile)
# Check where the package is and assign the appropriate variables. # Check where the package is and assign the appropriate variables.
@ -85,52 +89,47 @@ def checkNeedsSponsorship(component):
# Is a team member, no sponsorship required. # Is a team member, no sponsorship required.
return False return False
def checkExistingReports(package, isNewPackage): def checkExistingReports(package):
""" Check existing bug reports on Launchpad for a possible sync request. """ Check existing bug reports on Launchpad for a possible sync request.
If found ask for confirmation on filing a request. If found ask for confirmation on filing a request.
""" """
# Determine if the package is new or not.
if isNewPackage: launchpad = None
# Package not in Ubuntu, check Ubuntu Archive Team's bug page for
# possible duplicate reports.
bugListUrl = "https://bugs.launchpad.net/~ubuntu-archive"
else:
# Package in Ubuntu, check the package's bug list for duplicate reports.
bugListUrl = "https://bugs.launchpad.net/ubuntu/+source/%s" % package
try: try:
import launchpadbugs.connector as Connector launchpad = common.get_launchpad("ubuntu-dev-tools")
except: except ImportError:
# Failed to import launchpadbugs - skip check. print >> sys.stderr, 'Importing launchpadlib failed. Is ' \
print >> sys.stderr, "Unable to import launchpadbugs. Is " \ 'python-launchpadlib installed?'
"python-launchpad-bugs installed?" except IOError, msg:
# No credentials found.
print msg
# Failed to get Launchpad credentials.
if launchpad is None:
print >> sys.stderr, "Skipping existing report check, you should "\ print >> sys.stderr, "Skipping existing report check, you should "\
"manually check at:" "manually see if there are any at:"
print "-", bugListUrl print "- https://bugs.launchpad.net/ubuntu/+source/%s" % package
return print ""
return False
# Connect to the bug list. # Fetch the package's bug list from Launchpad.
bugList = Connector.ConnectBugList() pkg = launchpad.distributions["ubuntu"].getSourcePackage(name=package)
pkgBugList = pkg.searchTasks()
# Fetch data from Launchpad.
pkgBugList = bugList(bugListUrl)
if len(pkgBugList) == 0:
return # No bugs found.
# Search bug list for other sync requests. # Search bug list for other sync requests.
matchingBugs = [bug for bug in pkgBugList if "Please sync %s" % matchingBugs = [bug for bug in pkgBugList if "Please sync %s" %
package in bug.summary] package in bug.title]
if len(matchingBugs) == 0: if len(matchingBugs) == 0:
return # No sync bugs found. return # No sync bugs found.
print "The following bugs could possibly be duplicate sync request(s) on Launchpad:" print "The following bugs could be possible duplicate sync bug(s) on Launchpad:"
for bug in matchingBugs: for bug in matchingBugs:
print " *", bug.summary print " *", bug.title
print " -", bug.url print " -", common.translate_api_web(bug.self_link)
print "Please check the above URLs to verify this before filing a " \ print "Please check the above URLs to verify this before filing a " \
"possible duplicate report." "possible duplicate report."
@ -317,7 +316,7 @@ def mail_bug(source_package, subscribe, status, bugtitle, bugtext, keyid = None)
(mailserver, mailserver_port, s[1], s[0]) (mailserver, mailserver_port, s[1], s[0])
print "The port %s may be firewalled. Please try using requestsync with" \ print "The port %s may be firewalled. Please try using requestsync with" \
% mailserver_port % mailserver_port
print "the '--lp' flag to file a sync request with the launchpadbugs " \ print "the '--lp' flag to file a sync request with the launchpadlib " \
"module." "module."
return False return False
@ -343,24 +342,27 @@ def mail_bug(source_package, subscribe, status, bugtitle, bugtext, keyid = None)
return True return True
def post_bug(source_package, subscribe, status, bugtitle, bugtext): def post_bug(source_package, subscribe, status, bugtitle, bugtext):
'''Use python-launchpad-bugs to submit the sync request. '''Use python-launchpadlib to submit the sync request.
Return True if successfully posted, otherwise False.''' Return True if successfully posted, otherwise False.'''
import glob, os.path import glob, os.path
try: try:
import launchpadbugs.connector launchpad = common.get_launchpad("ubuntu-dev-tools")
from launchpadbugs.lpconstants import HTTPCONNECTION
except ImportError: except ImportError:
print >> sys.stderr, 'Importing launchpadbugs failed. Is python-launchpad-bugs installed?' print >> sys.stderr, 'Importing launchpadlib failed. Is python-launchpadlib installed?'
return False return False
except IOError, msg:
# No credentials found.
print msg
sys.exit(1)
if source_package: if source_package:
product = {'name': source_package, 'target': 'ubuntu'} product_url = "%subuntu/+source/%s" %(launchpad._root_uri, source_package)
else: else:
# new source package # new source package
product = {'name': 'ubuntu'} product_url = "%subuntu" %launchpad._root_uri
in_confirm_loop = True in_confirm_loop = True
while in_confirm_loop: while in_confirm_loop:
print 'Summary:\n%s\n\nDescription:\n%s' % (bugtitle, bugtext) print 'Summary:\n%s\n\nDescription:\n%s' % (bugtitle, bugtext)
@ -379,32 +381,17 @@ def post_bug(source_package, subscribe, status, bugtitle, bugtext):
print "Invalid answer." print "Invalid answer."
# Create bug # Create bug
Bug = launchpadbugs.connector.ConnectBug() bug = launchpad.bugs.createBug(description=bugtext, title=bugtitle, target=product_url)
# Force the usage of stable Launchpad.
Bug.set_connection_mode(HTTPCONNECTION.MODE.STABLE)
# Use our cookie file for authentication.
Bug.authentication = launchpad_cookiefile
print "Using LP cookie at: %s for authentication." % launchpad_cookiefile
# Submit bug report.
bug = Bug.New(product = product, summary = bugtitle, description = bugtext)
sleep(2) # Wait in case of slow Launchpad.
try:
bug.importance = 'Wishlist'
except IOError, s:
print "Warning: setting importance failed: %s" % s
bug.status = status #newly created bugreports have one task
bug.subscriptions.add(subscribe) task = bug.bug_tasks[0]
sleep(1) # Wait. task.transitionToImportance(importance='Wishlist')
task.transitionToStatus(status=status)
bug.commit()
sleep(1) # Wait. subscribe_url = "%s~%s" %(launchpad._root_uri, subscribe)
bug.subscribe(person=subscribe_url)
print 'Sync request filed as bug #%i: https://launchpad.net/bugs/%i' % (bug.bugnumber, bug.bugnumber)
print 'Sync request filed as bug #%i: %s' % (bug.id, common.translate_api_web(bug.self_link))
return True return True
def edit_report(subject, body, changes_required=False): def edit_report(subject, body, changes_required=False):
@ -477,7 +464,7 @@ if __name__ == '__main__':
help = "Whether package to sync is a new package in Ubuntu.") help = "Whether package to sync is a new package in Ubuntu.")
optParser.add_option("--lp", action = "store_true", optParser.add_option("--lp", action = "store_true",
dest = "lpbugs", default = False, dest = "lpbugs", default = False,
help = "Specify whether to use the launchpadbugs module for filing " \ help = "Specify whether to use the launchpadlib module for filing " \
"report.") "report.")
optParser.add_option("-s", action = "store_true", optParser.add_option("-s", action = "store_true",
dest = "sponsor", default = False, dest = "sponsor", default = False,
@ -524,8 +511,8 @@ if __name__ == '__main__':
# -s flag not specified - check if we do need sponsorship. # -s flag not specified - check if we do need sponsorship.
if not sponsorship: sponsorship = checkNeedsSponsorship(component) if not sponsorship: sponsorship = checkNeedsSponsorship(component)
# Check for existing sync requests. # Check for existing package reports.
checkExistingReports(srcpkg, newsource) if not newsource: checkExistingReports(srcpkg)
# Generate bug report. # Generate bug report.
subscribe = 'ubuntu-archive' subscribe = 'ubuntu-archive'

View File

@ -24,6 +24,7 @@ setup(name='ubuntu-dev-tools',
'get-build-deps', 'get-build-deps',
'grab-attachments', 'grab-attachments',
'hugdaylist', 'hugdaylist',
'manage-credentials',
'massfile', 'massfile',
'mk-sbuild-lv', 'mk-sbuild-lv',
'pbuilder-dist', 'pbuilder-dist',
@ -37,7 +38,7 @@ setup(name='ubuntu-dev-tools',
'suspicious-source', 'suspicious-source',
'ubuntu-iso', 'ubuntu-iso',
'update-maintainer', 'update-maintainer',
'what-patch', 'what-patch',
], ],
packages=['ubuntutools'], packages=['ubuntutools'],
) )

View File

@ -31,6 +31,16 @@ import re
import subprocess import subprocess
import sys import sys
import urllib2 import urllib2
import urlparse
import urllib
try:
import httplib2
from launchpadlib.credentials import Credentials
from launchpadlib.launchpad import Launchpad, STAGING_SERVICE_ROOT, EDGE_SERVICE_ROOT
from launchpadlib.errors import HTTPError
except ImportError:
Credentials = None
Launchpad = None
# Clear https_proxy env var as it's not supported in urllib/urllib2; see # Clear https_proxy env var as it's not supported in urllib/urllib2; see
# LP #122551 # LP #122551
@ -265,3 +275,120 @@ def packageComponent(package, release):
component = rel.split('/')[1] component = rel.split('/')[1]
return component.strip() return component.strip()
def find_credentials(consumer, files, level=None):
""" search for credentials matching 'consumer' in path for given access level. """
if Credentials is None:
raise ImportError
for f in files:
cred = Credentials()
try:
cred.load(open(f))
except:
continue
if cred.consumer.key == consumer:
return cred
raise IOError("No credentials found for '%s', please see the " \
"manage-credentials manpage for help on how to create " \
"one for this consumer." % consumer)
def get_credentials(consumer, cred_file=None, level=None):
files = list()
if cred_file:
files.append(cred_file)
if "LPCREDENTIALS" in os.environ:
files.append(os.environ["LPCREDENTIALS"])
files.append(os.path.join(os.getcwd(), "lp_credentials.txt"))
# Add all files which have our consumer name to file listing.
for x in glob.glob(os.path.expanduser("~/.cache/lp_credentials/%s*.txt" % \
consumer)):
files.append(x)
return find_credentials(consumer, files, level)
def get_launchpad(consumer, server=EDGE_SERVICE_ROOT, cache=None,
cred_file=None, level=None):
credentials = get_credentials(consumer, cred_file, level)
cache = cache or os.environ.get("LPCACHE", None)
return Launchpad(credentials, server, cache)
def query_to_dict(query_string):
result = dict()
options = filter(None, query_string.split("&"))
for opt in options:
key, value = opt.split("=")
result.setdefault(key, set()).add(value)
return result
def translate_web_api(url, launchpad):
scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
query = query_to_dict(query)
if not (("edge" in netloc and "edge" in str(launchpad._root_uri))
or ("staging" in netloc and "staging" in str(launchpad._root_uri))):
raise ValueError("url conflict (url: %s, root: %s" %(url, launchpad._root_uri))
if path.endswith("/+bugs"):
path = path[:-6]
if "ws.op" in query:
raise ValueError("Invalid web url, url: %s" %url)
query["ws.op"] = "searchTasks"
scheme, netloc, api_path, _, _ = urlparse.urlsplit(str(launchpad._root_uri))
query = urllib.urlencode(query)
url = urlparse.urlunsplit((scheme, netloc, api_path + path.lstrip("/"), query, fragment))
return url
def translate_api_web(self_url):
return self_url.replace("api.", "").replace("beta/", "")
LEVEL = {
0: "UNAUTHORIZED",
1: "READ_PUBLIC",
2: "WRITE_PUBLIC",
3: "READ_PRIVATE",
4: "WRITE_PRIVATE"
}
def approve_application(credentials, email, password, level, web_root,
context):
authorization_url = credentials.get_request_token(context, web_root)
if level in LEVEL:
level = 'field.actions.%s' %LEVEL[level]
elif level in LEVEL.values():
level = 'field.actions.%s' %level
elif str(level).startswith("field.actions") and str(level).split(".")[-1] in LEVEL:
pass
else:
raise ValueError("Unknown access level '%s'" %level)
params = {level: 1,
"oauth_token": credentials._request_token.key,
"lp.context": context or ""}
lp_creds = ":".join((email, password))
basic_auth = "Basic %s" %(lp_creds.encode('base64'))
headers = {'Authorization': basic_auth}
response, content = httplib2.Http().request(authorization_url,
method="POST", body=urllib.urlencode(params), headers=headers)
if int(response["status"]) != 200:
if not 300 <= int(response["status"]) <= 400: # this means redirection
raise HTTPError(response, content)
credentials.exchange_request_token_for_access_token(web_root)
return credentials
def translate_service(service):
_service = service.lower()
if _service in (STAGING_SERVICE_ROOT, EDGE_SERVICE_ROOT):
return _service
elif _service == "edge":
return EDGE_SERVICE_ROOT
elif _service == "staging":
return STAGING_SERVICE_ROOT
else:
raise ValueError("unknown service '%s'" %service)