Merge with trunk

This commit is contained in:
Iain Lane 2009-01-25 00:05:36 +00:00
commit 44d1d459da
24 changed files with 684 additions and 386 deletions

8
.bzrignore Normal file
View File

@ -0,0 +1,8 @@
/.shelf/
/build/
/python-build-stamp-*
/debian/files
/debian/ubuntu-dev-tools/
/debian/ubuntu-dev-tools.debhelper.log
/debian/ubuntu-dev-tools.*.debhelper
/debian/ubuntu-dev-tools.substvars

17
buildd
View File

@ -31,7 +31,10 @@ from optparse import OptionParser
from urllib import urlencode from urllib import urlencode
# ubuntu-dev-tools modules. # ubuntu-dev-tools modules.
from ubuntutools import common import ubuntutools.lp.cookie as lp_cookie
import ubuntutools.lp.functions as lp_functions
import ubuntutools.lp.urlopener as lp_urlopener
from ubuntutools import packages
# Usage. # Usage.
usage = "%prog <srcpackage> <release> <operation>\n\n" usage = "%prog <srcpackage> <release> <operation>\n\n"
@ -93,17 +96,17 @@ else:
oneArch = False oneArch = False
# Prepare Launchpad cookie. # Prepare Launchpad cookie.
launchpadCookie = common.prepareLaunchpadCookie() launchpadCookie = lp_cookie.prepareLaunchpadCookie()
urlopener = common.setupLaunchpadUrlOpener(launchpadCookie) urlopener = lp_urlopener.setupLaunchpadUrlOpener(launchpadCookie)
# Check the release exists. # Check the release exists.
common.checkReleaseExists(release) packages.checkReleaseExists(release)
# Find out the version in given release. # Find out the version in given release.
(page, version) = common.checkSourceExists(package, release) (page, version) = packages.checkSourceExists(package, release)
# Get the component the package is in. # Get the component the package is in.
component = common.packageComponent(package, release) component = packages.packageComponent(package, release)
# Output details. # Output details.
print "The source version for '%s' in %s (%s) is at %s." % (package, print "The source version for '%s' in %s (%s) is at %s." % (package,
@ -150,7 +153,7 @@ if op == "retry":
else: else:
teamNeeded = "ubuntu-dev" teamNeeded = "ubuntu-dev"
necessaryPrivs = common.isLPTeamMember(teamNeeded) necessaryPrivs = lp_functions.isLPTeamMember(teamNeeded)
if not necessaryPrivs: if not necessaryPrivs:
print >> sys.stderr, "You cannot perform the %s operation on a %s package " \ print >> sys.stderr, "You cannot perform the %s operation on a %s package " \

28
debian/changelog vendored
View File

@ -1,8 +1,30 @@
ubuntu-dev-tools (0.58) UNRELEASED; urgency=low ubuntu-dev-tools (0.60) UNRELEASED; urgency=low
* Changes go here. * ubuntutools/common.py: Now split into multiple files depending on
function.
* Adjusted imports on all files as necessary for the change above.
-- Jonathan Davies <jpds@ubuntu.com> Sat, 17 Jan 2009 21:04:55 +0000 -- Jonathan Davies <jpds@ubuntu.com> Mon, 19 Jan 2009 18:00:15 +0000
ubuntu-dev-tools (0.59) jaunty; urgency=low
* Move /etc/bash_completion.d/pbuilder-dist/pbuilder-dist created in
pre-0.30 versions to /etc/bash_completion.d/pbuilder-dist in the preinst.
-- Loic Minier <lool@dooz.org> Mon, 19 Jan 2009 18:02:55 +0100
ubuntu-dev-tools (0.58) jaunty; urgency=low
[ Loic Minier ]
* Fix a bunch of hyphen-used-as-minus-sign lintian informational tags.
* Don't repeat Section in the binary package's control chunk (pleases
lintian).
* New script, lp-set-dup, allows marking a bug and all its dups as a
duplicate of a new main bug.
* Re-add debian/pycompat to have an idempotent clean:: as cdbs creates the
file during clean; Debian #512300.
-- Loic Minier <lool@dooz.org> Mon, 19 Jan 2009 17:45:26 +0100
ubuntu-dev-tools (0.57) jaunty; urgency=low ubuntu-dev-tools (0.57) jaunty; urgency=low

1
debian/control vendored
View File

@ -12,7 +12,6 @@ Standards-Version: 3.8.0
Package: ubuntu-dev-tools Package: ubuntu-dev-tools
Architecture: all Architecture: all
Section: devel
Depends: ${python:Depends}, binutils, devscripts, sudo, python-debian, Depends: ${python:Depends}, binutils, devscripts, sudo, python-debian,
python-launchpad-bugs (>= 0.2.25), python-launchpadlib, dctrl-tools, python-launchpad-bugs (>= 0.2.25), python-launchpadlib, dctrl-tools,
lsb-release, diffstat, dpkg-dev, ${misc:Depends} lsb-release, diffstat, dpkg-dev, ${misc:Depends}

1
debian/pycompat vendored Normal file
View File

@ -0,0 +1 @@
2

20
debian/ubuntu-dev-tools.preinst vendored Normal file
View File

@ -0,0 +1,20 @@
#!/bin/sh
set -e
if [ -e /etc/bash_completion.d/pbuilder-dist/pbuilder-dist ]; then
tmp_file="$(mktemp /etc/bash_completion.d/pbuilder-dist.XXXXXX)"
mv -fv /etc/bash_completion.d/pbuilder-dist/pbuilder-dist \
"$tmp_file"
rmdir --ignore-fail-on-non-empty /etc/bash_completion.d/pbuilder-dist
# dir non-empty
if [ -d /etc/bash_completion.d/pbuilder-dist ]; then
echo "W: /etc/bash_completion.d/pbuilder-dist not empty; moving /etc/bash_completion.d/pbuilder-dist out of the way"
mv -fv /etc/bash_completion.d/pbuilder-dist /etc/bash_completion.d/pbuilder-dist.dpkg-disabled
fi
mv -fv "$tmp_file" /etc/bash_completion.d/pbuilder-dist
fi
#DEBHELPER#

View File

@ -50,13 +50,13 @@ Launchpad token. 0 is unauthorized, 1 is read public data, 2; write public data,
There are currently two ways of using \fBmanage-credentials\fR to get There are currently two ways of using \fBmanage-credentials\fR to get
Launchpad tokens. Launchpad tokens.
.TP .TP
1) manage-credentials create -c CONSUMER --level 2 1) manage-credentials create \-c CONSUMER \-\-level 2
.TP .TP
This way shall open your webbrowser with a Launchpad login page. This way shall open your webbrowser with a Launchpad login page.
.TP .TP
2) manage-credentials create -c CONSUMER --level 2 --password BOO --email me@example.com 2) manage-credentials create \-c CONSUMER \-\-level 2 \-\-password BOO \-\-email me@example.com
.TP .TP
This is a hack, but it works and does not require a webbrowser . This is a hack, but it works and does not require a webbrowser .
@ -66,7 +66,7 @@ If you intend to use manage-credentials for Ubuntu development (such as
the ubuntu-dev-tools package). Please by sure to run the following: the ubuntu-dev-tools package). Please by sure to run the following:
.TP .TP
manage-credentials create -c ubuntu-dev-tools -l 2 manage-credentials create \-c ubuntu-dev-tools \-l 2
.SH AUTHOR .SH AUTHOR
.B manage-credentials .B manage-credentials

View File

@ -27,7 +27,7 @@ What personality to use (defaults to match \-\-arch).
Turn on script debugging. Turn on script debugging.
.TP .TP
.B \-\-skip\-updates .B \-\-skip\-updates
Do not include the -updates pocket in the installed sources.list. Do not include the \-updates pocket in the installed sources.list.
.TP .TP
.B \-\-source\-template=FILE .B \-\-source\-template=FILE
Use FILE as the sources.list template (defaults to $HOME/.mk\-sbuild\-lv.sources). Use FILE as the sources.list template (defaults to $HOME/.mk\-sbuild\-lv.sources).
@ -48,7 +48,7 @@ Size of snapshot LVs (defaults to 4G).
Lines to append to schroot entries. Lines to append to schroot entries.
.TP .TP
.B SKIP_UPDATES .B SKIP_UPDATES
Do not include the -updates pocket in the installed sources.list. Do not include the \-updates pocket in the installed sources.list.
.SH FILES .SH FILES
.TP .TP
@ -66,7 +66,7 @@ Can contain a customized configuration section to be inserted into
See schroot.conf(5) for more details on the format. See schroot.conf(5) for more details on the format.
.SH USING THE CHROOTS .SH USING THE CHROOTS
.TP .TP
To UPDATE the golden image: \fBschroot \-c ${CHROOT_NAME}\-source -u root \-\- sh \-c apt\-get update && apt\-get \-y upgrade\fR To UPDATE the golden image: \fBschroot \-c ${CHROOT_NAME}\-source \-u root \-\- sh \-c apt\-get update && apt\-get \-y upgrade\fR
.TP .TP
To ENTER an image snapshot: \fBschroot \-c ${CHROOT_NAME}\fR To ENTER an image snapshot: \fBschroot \-c ${CHROOT_NAME}\fR
.TP .TP

View File

@ -20,7 +20,7 @@
import os import os
import sys import sys
from ubuntutools.common import get_launchpad from ubuntutools.lp.libsupport import get_launchpad
USAGE = "grab-attachments <bug numbers>" USAGE = "grab-attachments <bug numbers>"
@ -32,6 +32,7 @@ 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)
launchpad = get_launchpad("ubuntu-dev-tools") launchpad = get_launchpad("ubuntu-dev-tools")
for arg in sys.argv[1:]: for arg in sys.argv[1:]:
try: try:
@ -39,7 +40,9 @@ def main():
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 = launchpad.bugs[number] b = launchpad.bugs[number]
for a in b.attachments: for a in b.attachments:
f = a.data.open() f = a.data.open()
filename = os.path.join(os.getcwd(), f.filename) filename = os.path.join(os.getcwd(), f.filename)

View File

@ -35,7 +35,7 @@ import string
import sys import sys
from optparse import OptionParser from optparse import OptionParser
from ubuntutools.common import get_launchpad, translate_web_api, translate_api_web from ubuntutools.lp.libsupport import get_launchpad, translate_web_api, translate_api_web
def check_args(): def check_args():
howmany = -1 howmany = -1

98
lp-set-dup Executable file
View File

@ -0,0 +1,98 @@
#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""Sets the "duplicate of" bug of a bug and its dups."""
# Copyright (c) 2009 Canonical Ltd.
#
# lp-set-dup 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 2, or (at your option) any
# later version.
#
# lp-set-dup 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.
#
# You should have received a copy of the GNU General Public License
# along with lp-set-dup; see the file COPYING. If not, write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
# Authors:
# Loïc Minier <lool@dooz.org>
import os, sys
from optparse import OptionParser
from ubuntutools.lp.libsupport as lp_libsupport
def die(message):
print >> sys.stderr, "Fatal: " + message
sys.exit(1)
if __name__ == '__main__':
usage = "Usage: %prog [-f] <new main bug> <bug to dup> [<bug to dup>...]"
optParser = OptionParser(usage)
optParser.add_option("-f", action = "store_true",
dest = "force", default = False,
help = "Skip confirmation prompt")
(options, args) = optParser.parse_args()
if len(args) < 2:
optParser.error("Need at least a new main bug and a bug to dup")
launchpad = None
try:
print "Setting up Launchpad"
launchpad = lp_libsupport.get_launchpad("ubuntu-dev-tools")
print "Launchpad setup complete"
except ImportError:
suggestion = "check whether python-launchpadlib is installed"
except IOError:
suggestion = "you might want to \"manage-credentials create --consumer ubuntu-dev-tools --level 2\""
if launchpad is None:
die("Couldn't setup Launchpad for the ubuntu-dev-tools consumer; %s" (suggestion, ))
# check that the new main bug isn't a duplicate
new_main_bug = launchpad.bugs[args[0]]
new_main_dup_of = new_main_bug.duplicate_of
if new_main_dup_of is not None:
s = None
try:
s = raw_input("Bug %s is a duplicate of %s; would you like to use %s as the new main bug instead? [y/N]" % (new_main_bug.id, new_main_dup_of.id, new_main_dup_of.id))
except:
die("Aborted")
if s.lower() not in ("y", "yes"):
die("User aborted")
new_main_bug = new_main_dup_of
# build list of bugs to process, first the dups then the bug
bugs_to_process = []
for b in args[1:]:
print "Processing %s" % (b)
bug = launchpad.bugs[b]
dups = bug.duplicates
if dups is not None:
bugs_to_process.extend(dups)
print "Found %i dups for %s" % (len(dups), b)
bugs_to_process.append(bug)
# process dups first, then their main bug
print "Would set the following bugs as duplicates of %s: %s" % (new_main_bug.id, " ".join([str(b.id) for b in bugs_to_process]))
if not options.force:
s = None
try:
s = raw_input("Proceed? [y/N]")
except:
die("Aborted")
if s.lower() not in ("y", "yes"):
die("User aborted")
for bug in bugs_to_process:
print "Marking bug %s as a duplicate of %s" % (bug.id, new_main_bug.id)
bug.duplicate_of = new_main_bug
bug.lp_save()

View File

@ -22,9 +22,8 @@
import os import os
import sys import sys
from optparse import OptionParser, make_option from optparse import OptionParser, make_option
from ubuntutools.common import Credentials, Launchpad, translate_service from ubuntutools.lp.libsupport import *
from ubuntutools.common import LEVEL, translate_api_web, approve_application from ubuntutools.misc import mkdir
from ubuntutools.common import mkdir
class CmdOptions(OptionParser): class CmdOptions(OptionParser):

View File

@ -6,6 +6,8 @@
# Modified by Iain Lane <iain@orangesquash.org.uk>, taking some code written by # Modified by Iain Lane <iain@orangesquash.org.uk>, taking some code written by
# Daniel Hahler <ubuntu@thequod.de> # Daniel Hahler <ubuntu@thequod.de>
# #
# python-launchpadlib support was added by Markus Korn <thekorn@gmx.de>.
#
# ################################################################## # ##################################################################
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
@ -27,7 +29,7 @@ import email
import subprocess import subprocess
import glob import glob
from ubuntutools.common import get_launchpad, translate_api_web, translate_web_api from ubuntutools.lp.libsupport import get_launchpad, translate_api_web, translate_web_api
def read_config(): def read_config():
instructions_file = open("instructions") instructions_file = open("instructions")

View File

@ -33,7 +33,7 @@ import urllib2
from optparse import OptionParser from optparse import OptionParser
# Ubuntu-dev-tools modules. # Ubuntu-dev-tools modules.
from ubuntutools import common from ubuntutools import packages
class BackportFromLP: class BackportFromLP:
@ -65,10 +65,7 @@ if __name__ == '__main__':
optParser = OptionParser(usage) optParser = OptionParser(usage)
(options, args) = optParser.parse_args() (options, args) = optParser.parse_args()
if not args: if not args: optParser.error("Arguments required.")
print >> sys.stderr, "Need arguments."
optParser.print_help()
sys.exit(1)
package = str(args[0]).lower() package = str(args[0]).lower()
@ -79,10 +76,10 @@ if __name__ == '__main__':
# Correct-ish args, can proceed. # Correct-ish args, can proceed.
# Check release by checking if Launchpad page exists # Check release by checking if Launchpad page exists
common.checkReleaseExists(release) packages.checkReleaseExists(release)
# Check package exists. # Check package exists.
common.checkSourceExists(package, release) packages.checkSourceExists(package, release)
# All good - start downloading... # All good - start downloading...
try: try:

View File

@ -35,9 +35,13 @@ from debian_bundle.changelog import Version
from optparse import OptionParser from optparse import OptionParser
from time import sleep from time import sleep
from ubuntutools import common # ubuntu-dev-tools modules.
import ubuntutools.lp.cookie as lp_cookie
import ubuntutools.lp.functions as lp_functions
import ubuntutools.lp.libsupport as lp_libsupport
import ubuntutools.lp.urlopener as lp_urlopener
launchpad_cookiefile = common.prepareLaunchpadCookie() launchpad_cookiefile = lp_cookie.prepareLaunchpadCookie()
def checkNeedsSponsorship(component): def checkNeedsSponsorship(component):
""" """
@ -58,7 +62,7 @@ def checkNeedsSponsorship(component):
# >>> me = launchpad.me # >>> me = launchpad.me
# >>> me.inTeam(<TEAM>) #or # >>> me.inTeam(<TEAM>) #or
# >>> me in <TEAM> # >>> me in <TEAM>
urlopener = common.setupLaunchpadUrlOpener(launchpad_cookiefile) urlopener = lp_urlopener.setupLaunchpadUrlOpener(launchpad_cookiefile)
# Check where the package is and assign the appropriate variables. # Check where the package is and assign the appropriate variables.
if component in ['main', 'restricted']: if component in ['main', 'restricted']:
@ -69,7 +73,7 @@ def checkNeedsSponsorship(component):
sponsor = "ubuntu-universe-sponsors" sponsor = "ubuntu-universe-sponsors"
# Check if they are a member of the team. # Check if they are a member of the team.
teamMember = common.isLPTeamMember(team) teamMember = lp_functions.isLPTeamMember(team)
if not teamMember: if not teamMember:
print "You are not a member (direct or indirect) of the '%s' " \ print "You are not a member (direct or indirect) of the '%s' " \
@ -98,7 +102,7 @@ def checkExistingReports(package):
launchpad = None launchpad = None
try: try:
launchpad = common.get_launchpad("ubuntu-dev-tools") launchpad = lp_libsupport.get_launchpad("ubuntu-dev-tools")
except ImportError: except ImportError:
print >> sys.stderr, 'Importing launchpadlib failed. Is ' \ print >> sys.stderr, 'Importing launchpadlib failed. Is ' \
'python-launchpadlib installed?' 'python-launchpadlib installed?'
@ -129,7 +133,7 @@ def checkExistingReports(package):
for bug in matchingBugs: for bug in matchingBugs:
print " *", bug.title print " *", bug.title
print " -", common.translate_api_web(bug.self_link) print " -", lp_libsupport.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."
@ -348,7 +352,7 @@ def post_bug(source_package, subscribe, status, bugtitle, bugtext):
import glob, os.path import glob, os.path
try: try:
launchpad = common.get_launchpad("ubuntu-dev-tools") launchpad = lp_libsupport.get_launchpad("ubuntu-dev-tools")
except ImportError: except ImportError:
print >> sys.stderr, 'Importing launchpadlib failed. Is python-launchpadlib installed?' print >> sys.stderr, 'Importing launchpadlib failed. Is python-launchpadlib installed?'
return False return False
@ -391,7 +395,8 @@ def post_bug(source_package, subscribe, status, bugtitle, bugtext):
subscribe_url = "%s~%s" %(launchpad._root_uri, subscribe) subscribe_url = "%s~%s" %(launchpad._root_uri, subscribe)
bug.subscribe(person=subscribe_url) bug.subscribe(person=subscribe_url)
print 'Sync request filed as bug #%i: %s' % (bug.id, common.translate_api_web(bug.self_link)) print 'Sync request filed as bug #%i: %s' % (bug.id,
lp_libsupport.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):

View File

@ -24,6 +24,7 @@ setup(name='ubuntu-dev-tools',
'get-build-deps', 'get-build-deps',
'grab-attachments', 'grab-attachments',
'hugdaylist', 'hugdaylist',
'lp-set-dup',
'manage-credentials', 'manage-credentials',
'massfile', 'massfile',
'mk-sbuild-lv', 'mk-sbuild-lv',

View File

@ -33,14 +33,6 @@ import sys
import urllib2 import urllib2
import urlparse import urlparse
import urllib 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
@ -48,347 +40,7 @@ if os.environ.has_key('https_proxy'):
print >> sys.stderr, "Ignoring https_proxy (no support in urllib/urllib2; see LP #122551)" print >> sys.stderr, "Ignoring https_proxy (no support in urllib/urllib2; see LP #122551)"
del os.environ['https_proxy'] del os.environ['https_proxy']
def mkdir(directory):
""" Create the given directory and all its parents recursively, but don't
raise an exception if it already exists. """
path = [x for x in directory.split('/') if x]
for i in xrange(len(path)):
current_path = '/' + '/'.join(path[:i+1])
if not os.path.isdir(current_path):
os.mkdir(current_path)
def readlist(filename, uniq=True):
""" Read a list of words from the indicated file. """
if not os.path.isfile(filename):
print 'File "%s" does not exist.' % filename
return False
content = open(filename).read().replace('\n', ' ').replace(',', ' ')
if not content.strip():
print 'File "%s" is empty.' % filename
return False
items = [item for item in content.split() if item]
if uniq:
items = list(set(items))
return items
def checkReleaseExists(release):
""" Check that an Ubuntu release exists by opening
https://launchpad.net/ubuntu/releaseName page on Launchpad.
If an error is returned; the release does not exist. """
release = release.split('-')[0] # Remove pocket
try:
urllib2.urlopen("https://launchpad.net/ubuntu/%s" % release)
except urllib2.HTTPError:
print >> sys.stderr, "The Ubuntu '%s' release does not appear to " \
"exist on Launchpad." % release
sys.exit(1)
except urllib2.URLError, error: # Other error (NXDOMAIN, ...)
(_, reason) = error.reason
print >> sys.stderr, "Error while checking for Ubuntu '%s' " \
"release on Launchpad: %s." % (release, reason)
sys.exit(1)
def checkSourceExists(package, release):
""" Check that a package exists by opening its
https://launchpad.net/ubuntu/+source/package page.
Return the page and version in release. """
if '-' in release:
(release, pocket) = release.split('-', 1)
else:
pocket = 'release'
try:
page = urllib2.urlopen('https://launchpad.net/ubuntu/+source/' + package).read()
m = re.search('<td>%s</td>\s*\n.*"/ubuntu/%s/\+source/%s/(\d[^"]+)"' % (
pocket, release, package.replace('+', '\+')), page)
if not m:
print >> sys.stderr, "Unable to find source package '%s' in " \
"the %s-%s pocket." % (package, release.capitalize(), pocket)
sys.exit(1)
except urllib2.HTTPError, error: # Raised on 404.
if error.code == 404:
print >> sys.stderr, "The source package '%s' does not appear to " \
"exist in Ubuntu." % package
else: # Other error code, probably Launchpad malfunction.
print >> sys.stderr, "Error while checking Launchpad for Ubuntu " \
"package: %s." % error.code
sys.exit(1) # Exit. Error encountered.
except urllib2.URLError, error: # Other error (NXDOMAIN, ...)
(_, reason) = error.reason
print >> sys.stderr, "Error while checking Launchpad for Ubuntu " \
"package: %s." % reason
sys.exit(1)
# Get package version.
version = m.group(1)
return page, version
def prepareLaunchpadCookie():
""" Search for a cookie file in the places as defined by try_globs.
We shall use this cookie for authentication with Launchpad. """
# We do not have our cookie.
launchpad_cookiefile = None
# Look in common locations.
try_globs = ('~/.lpcookie.txt', '~/.mozilla/*/*/cookies.sqlite',
'~/.mozilla/*/*/cookies.txt')
cookie_file_list = []
if launchpad_cookiefile == None:
for try_glob in try_globs:
try:
cookie_file_list += glob.glob(os.path.expanduser(try_glob))
except:
pass
for cookie_file in cookie_file_list:
launchpad_cookiefile = _check_for_launchpad_cookie(cookie_file)
if launchpad_cookiefile != None:
break
# Unable to find a correct file.
if launchpad_cookiefile == None:
print >> sys.stderr, "Could not find cookie file for Launchpad. "
print >> sys.stderr, "Looked in: %s" % ", ".join(try_globs)
print >> sys.stderr, "You should be able to create a valid file by " \
"logging into Launchpad with Firefox."
sys.exit(1)
return launchpad_cookiefile
def _check_for_launchpad_cookie(cookie_file):
# Found SQLite file? Parse information from it.
if 'cookies.sqlite' in cookie_file:
import sqlite3 as sqlite
con = sqlite.connect(cookie_file)
cur = con.cursor()
try:
cur.execute("select host, path, isSecure, expiry, name, value from moz_cookies where host like ?", ['%%launchpad%%'])
except sqlite.OperationalError:
print 'Warning: Database "%s" is locked; ignoring it.' % cookie_file
return None
# No matching cookies? Abort.
items = cur.fetchall()
if len(items) == 0:
return None
ftstr = ["FALSE", "TRUE"]
# This shall be where our new cookie file lives - at ~/.lpcookie.txt
newLPCookieLocation = os.path.expanduser("~/.lpcookie.txt")
# Open file for writing.
try:
newLPCookie = open(newLPCookieLocation, 'w')
# For security reasons, change file mode to write and read
# only by owner.
os.chmod(newLPCookieLocation, 0600)
newLPCookie.write("# HTTP Cookie File for Launchpad.\n") # Header.
for item in items:
# Write entries.
newLPCookie.write("%s\t%s\t%s\t%s\t%s\t%s\t%s\n" % (
item[0], ftstr[item[0].startswith('.')], item[1],
ftstr[item[2]], item[3], item[4], item[5]))
finally:
newLPCookie.close() # And close file.
return newLPCookieLocation
else:
if open(cookie_file).read().find('launchpad.net') != -1:
return cookie_file
return None
def setupLaunchpadUrlOpener(cookie):
""" Build HTML opener with cookie file. """
# Attempt to load our cookie file.
try:
cj = cookielib.MozillaCookieJar()
cj.load(cookie)
except cookielib.LoadError, error:
print "Unable to load cookie file: %s (%s)" % (cookie, error)
sys.exit(1)
# Add cookie to our URL opener.
urlopener = urllib2.build_opener()
urlopener.add_handler(urllib2.HTTPCookieProcessor(cj))
return urlopener
def isLPTeamMember(team):
""" Checks if the user is a member of a certain team on Launchpad.
We do this by opening the team page on Launchpad and checking if the
text "You are not a member of this team" is present using the
user's cookie file for authentication.
If the user is a member of the team: return True.
If the user is not a member of the team: return False.
"""
# TODO: Check if launchpadlib may be a better way of doing this.
# Prepare cookie.
cookieFile = prepareLaunchpadCookie()
# Prepare URL opener.
urlopener = setupLaunchpadUrlOpener(cookieFile)
# Try to open the Launchpad team page:
try:
lpTeamPage = urlopener.open("https://launchpad.net/~%s" % team).read()
except urllib2.HTTPError, error:
print >> sys.stderr, "Unable to connect to Launchpad. Received a %s." % error.code
sys.exit(1)
# Check if text is present in page.
if ("You are not a member of this team") in lpTeamPage:
return False
return True
def packageComponent(package, release):
madison = subprocess.Popen(['rmadison', '-u', 'ubuntu', '-a', 'source', \
'-s', release, package], stdout = subprocess.PIPE)
out = madison.communicate()[0]
assert (madison.returncode == 0)
for l in out.splitlines():
(pkg, version, rel, builds) = l.split('|')
component = 'main'
if rel.find('/') != -1:
component = rel.split('/')[1]
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)

View File

@ -0,0 +1,3 @@
##
## ubuntu-dev-tools Launchpad Python modules.
##

106
ubuntutools/lp/cookie.py Normal file
View File

@ -0,0 +1,106 @@
#
# lpcookie.py - functions related to the creation of Launchpad cookie files
# and authentication.
#
# Copyright (C) 2008 Jonathan Davies <jpds@ubuntu.com>
# Copyright (C) 2008 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
#
# 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.
#
# Please see the /usr/share/common-licenses/GPL file for the full text of
# the GNU General Public License license.
#
# Modules.
import glob
import os
import sys
def prepareLaunchpadCookie():
"""
Search for a cookie file in the places as defined by try_globs.
We shall use this cookie for authentication with Launchpad.
"""
# We do not have our cookie.
launchpad_cookiefile = None
# Look in common locations.
try_globs = ('~/.lpcookie.txt', '~/.mozilla/*/*/cookies.sqlite',
'~/.mozilla/*/*/cookies.txt')
cookie_file_list = []
if launchpad_cookiefile == None:
for try_glob in try_globs:
try:
cookie_file_list += glob.glob(os.path.expanduser(try_glob))
except:
pass
for cookie_file in cookie_file_list:
launchpad_cookiefile = _check_for_launchpad_cookie(cookie_file)
if launchpad_cookiefile != None:
break
# Unable to find a correct file.
if launchpad_cookiefile == None:
print >> sys.stderr, "Could not find cookie file for Launchpad. "
print >> sys.stderr, "Looked in: %s" % ", ".join(try_globs)
print >> sys.stderr, "You should be able to create a valid file by " \
"logging into Launchpad with Firefox."
sys.exit(1)
return launchpad_cookiefile
def _check_for_launchpad_cookie(cookie_file):
# Found SQLite file? Parse information from it.
if 'cookies.sqlite' in cookie_file:
import sqlite3 as sqlite
con = sqlite.connect(cookie_file)
cur = con.cursor()
try:
cur.execute("select host, path, isSecure, expiry, name, value from moz_cookies where host like ?", ['%%launchpad%%'])
except sqlite.OperationalError:
print 'Warning: Database "%s" is locked; ignoring it.' % cookie_file
return None
# No matching cookies? Abort.
items = cur.fetchall()
if len(items) == 0:
return None
ftstr = ["FALSE", "TRUE"]
# This shall be where our new cookie file lives - at ~/.lpcookie.txt
newLPCookieLocation = os.path.expanduser("~/.lpcookie.txt")
# Open file for writing.
try:
newLPCookie = open(newLPCookieLocation, 'w')
# For security reasons, change file mode to write and read
# only by owner.
os.chmod(newLPCookieLocation, 0600)
newLPCookie.write("# HTTP Cookie File for Launchpad.\n") # Header.
for item in items:
# Write entries.
newLPCookie.write("%s\t%s\t%s\t%s\t%s\t%s\t%s\n" % (
item[0], ftstr[item[0].startswith('.')], item[1],
ftstr[item[2]], item[3], item[4], item[5]))
finally:
newLPCookie.close() # And close file.
return newLPCookieLocation
else:
if open(cookie_file).read().find('launchpad.net') != -1:
return cookie_file
return None

View File

@ -0,0 +1,33 @@
import cookie
import urlopener
def isLPTeamMember(team):
""" Checks if the user is a member of a certain team on Launchpad.
We do this by opening the team page on Launchpad and checking if the
text "You are not a member of this team" is present using the
user's cookie file for authentication.
If the user is a member of the team: return True.
If the user is not a member of the team: return False.
"""
# TODO: Check if launchpadlib may be a better way of doing this.
# Prepare cookie.
cookieFile = cookie.prepareLaunchpadCookie()
# Prepare URL opener.
urlopener = urlopener.setupLaunchpadUrlOpener(cookieFile)
# Try to open the Launchpad team page:
try:
lpTeamPage = urlopener.open("https://launchpad.net/~%s" % team).read()
except urllib2.HTTPError, error:
print >> sys.stderr, "Unable to connect to Launchpad. Received a %s." % error.code
sys.exit(1)
# Check if text is present in page.
if ("You are not a member of this team") in lpTeamPage:
return False
return True

View File

@ -0,0 +1,148 @@
#
# lplibsupport.py - functions which add launchpadlib support to the Ubuntu
# Developer Tools package.
#
# 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.
#
# Please see the /usr/share/common-licenses/GPL file for the full text of
# the GNU General Public License license.
#
# Modules.
import glob
import os
import urllib
import urlparse
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
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)

View File

@ -0,0 +1,40 @@
#
# lpurlopener.py - set up a special URL opener which uses a Launchpad cookie
# file for authentication.
#
# Copyright (C) 2008 Jonathan Davies <jpds@ubuntu.com>
#
# 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.
#
# Please see the /usr/share/common-licenses/GPL file for the full text of
# the GNU General Public License license.
#
# Modules.
import cookielib
import urllib2
def setupLaunchpadUrlOpener(cookie):
""" Build HTML opener with cookie file. """
# Attempt to load our cookie file.
try:
cj = cookielib.MozillaCookieJar()
cj.load(cookie)
except cookielib.LoadError, error:
print "Unable to load cookie file: %s (%s)" % (cookie, error)
sys.exit(1)
# Add cookie to our URL opener.
urlopener = urllib2.build_opener()
urlopener.add_handler(urllib2.HTTPCookieProcessor(cj))
return urlopener

55
ubuntutools/misc.py Normal file
View File

@ -0,0 +1,55 @@
#
# misc.py - misc functions for the Ubuntu Developer Tools scripts.
#
# Copyright (C) 2008 Jonathan Davies <jpds@ubuntu.com>
# Copyright (C) 2008 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
#
# 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.
#
# Please see the /usr/share/common-licenses/GPL file for the full text of
# the GNU General Public License license.
#
# Modules.
import os
def mkdir(directory):
"""
Create the given directory and all its parents recursively, but don't
raise an exception if it already exists.
"""
path = [x for x in directory.split('/') if x]
for i in xrange(len(path)):
current_path = '/' + '/'.join(path[:i+1])
if not os.path.isdir(current_path):
os.mkdir(current_path)
def readlist(filename, uniq=True):
""" Read a list of words from the indicated file. """
if not os.path.isfile(filename):
print 'File "%s" does not exist.' % filename
return False
content = open(filename).read().replace('\n', ' ').replace(',', ' ')
if not content.strip():
print 'File "%s" is empty.' % filename
return False
items = [item for item in content.split() if item]
if uniq:
items = list(set(items))
return items

103
ubuntutools/packages.py Normal file
View File

@ -0,0 +1,103 @@
#
# packages.py - functions related to Ubuntu source packages and releases.
#
# Copyright (C) 2008 Jonathan Davies <jpds@ubuntu.com>
# Copyright (C) 2008 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
#
# 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.
#
# Please see the /usr/share/common-licenses/GPL file for the full text of
# the GNU General Public License license.
#
# Modules.
import re
import subprocess
import sys
import urllib2
def checkReleaseExists(release):
"""
Check that an Ubuntu release exists by opening
https://launchpad.net/ubuntu/releaseName page on Launchpad.
If an error is returned; the release does not exist.
"""
release = release.split('-')[0] # Remove pocket
try:
urllib2.urlopen("https://launchpad.net/ubuntu/%s" % release)
except urllib2.HTTPError:
print >> sys.stderr, "The Ubuntu '%s' release does not appear to " \
"exist on Launchpad." % release
sys.exit(1)
except urllib2.URLError, error: # Other error (NXDOMAIN, ...)
(_, reason) = error.reason
print >> sys.stderr, "Error while checking for Ubuntu '%s' " \
"release on Launchpad: %s." % (release, reason)
sys.exit(1)
def checkSourceExists(package, release):
"""
Check that a package exists by opening its
https://launchpad.net/ubuntu/+source/package page.
Return the package's page URL and it's current version in the requested
release.
"""
if '-' in release:
(release, pocket) = release.split('-', 1)
else:
pocket = 'release'
try:
page = urllib2.urlopen('https://launchpad.net/ubuntu/+source/' + package).read()
m = re.search('<td>%s</td>\s*\n.*"/ubuntu/%s/\+source/%s/(\d[^"]+)"' % (
pocket, release, package.replace('+', '\+')), page)
if not m:
print >> sys.stderr, "Unable to find source package '%s' in " \
"the %s-%s pocket." % (package, release.capitalize(), pocket)
sys.exit(1)
except urllib2.HTTPError, error: # Raised on 404.
if error.code == 404:
print >> sys.stderr, "The source package '%s' does not appear to " \
"exist in Ubuntu." % package
else: # Other error code, probably Launchpad malfunction.
print >> sys.stderr, "Error while checking Launchpad for Ubuntu " \
"package: %s." % error.code
sys.exit(1) # Exit. Error encountered.
except urllib2.URLError, error: # Other error (NXDOMAIN, ...)
(_, reason) = error.reason
print >> sys.stderr, "Error while checking Launchpad for Ubuntu " \
"package: %s." % reason
sys.exit(1)
# Get package version.
version = m.group(1)
return page, version
def packageComponent(package, release):
"""
Use rmadison to see which component a package is in.
"""
madison = subprocess.Popen(['rmadison', '-u', 'ubuntu', '-a', 'source', \
'-s', release, package], stdout = subprocess.PIPE)
out = madison.communicate()[0]
assert (madison.returncode == 0)
for l in out.splitlines():
(pkg, version, rel, builds) = l.split('|')
component = 'main'
if rel.find('/') != -1:
component = rel.split('/')[1]
return component.strip()