~bzr-pqm/bzr/bzr.dev

2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
1
# Copyright (C) 2006 Canonical Ltd
0.4.4 by Martin Pool
Start forming xmlrpc requests
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
0.4.4 by Martin Pool
Start forming xmlrpc requests
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
0.4.14 by Martin Pool
Update xmlrpc api
18
from getpass import getpass
0.4.17 by Martin Pool
Allow xmlrpc service url to be overridden by $BZR_LP_XMLRPC_URL
19
import os
0.4.7 by Martin Pool
Start making provision to test using a mock xmlrpc transport.
20
from urlparse import urlsplit, urlunsplit
0.4.29 by Martin Pool
(register-branch) override xmlrpc user-agent; move Transport construction
21
import urllib
0.4.4 by Martin Pool
Start forming xmlrpc requests
22
import xmlrpclib
0.4.13 by Martin Pool
Update xmlrpc api to pass product name as a parameter.
23
3955.3.1 by Jonathan Lange
Start doing URL stuff, extracting the domain bit out of LaunchpadService,
24
from bzrlib.lazy_import import lazy_import
25
lazy_import(globals(), """
26
from bzrlib import urlutils
27
""")
28
2900.2.21 by Vincent Ladeuil
Make lp_registration aware of authentication config.
29
from bzrlib import (
30
    config,
31
    errors,
2900.2.22 by Vincent Ladeuil
Polishing.
32
    __version__ as _bzrlib_version,
2900.2.21 by Vincent Ladeuil
Make lp_registration aware of authentication config.
33
    )
1668.1.9 by Martin Pool
(launchpad plugin) Better reporting of errors from xmlrpc
34
35
# for testing, do
36
'''
37
export BZR_LP_XMLRPC_URL=http://xmlrpc.staging.launchpad.net/bazaar/
38
'''
0.4.13 by Martin Pool
Update xmlrpc api to pass product name as a parameter.
39
3193.5.2 by Tim Penhey
Updated the tests to handle unknown launchpad instances.
40
class InvalidLaunchpadInstance(errors.BzrError):
41
42
    _fmt = "%(lp_instance)s is not a valid Launchpad instance."
43
44
    def __init__(self, lp_instance):
45
        errors.BzrError.__init__(self, lp_instance=lp_instance)
46
47
3955.3.5 by Jonathan Lange
Add an untested plugin, make the error handling a little nicer.
48
class NotLaunchpadBranch(errors.BzrError):
49
4031.2.8 by Jonathan Lange
Say "registered on", not "hosted on".
50
    _fmt = "%(url)s is not registered on Launchpad."
3955.3.5 by Jonathan Lange
Add an untested plugin, make the error handling a little nicer.
51
52
    def __init__(self, url):
53
        errors.BzrError.__init__(self, url=url)
54
55
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
56
class LaunchpadService(object):
0.4.27 by Martin Pool
doc
57
    """A service to talk to Launchpad via XMLRPC.
3193.5.2 by Tim Penhey
Updated the tests to handle unknown launchpad instances.
58
0.4.27 by Martin Pool
doc
59
    See http://bazaar-vcs.org/Specs/LaunchpadRpc for the methods we can call.
60
    """
0.4.6 by Martin Pool
Put the rest of the parameters into the registration request.
61
3955.3.1 by Jonathan Lange
Start doing URL stuff, extracting the domain bit out of LaunchpadService,
62
    LAUNCHPAD_DOMAINS = {
63
        'production': 'launchpad.net',
64
        'edge': 'edge.launchpad.net',
65
        'staging': 'staging.launchpad.net',
66
        'demo': 'demo.launchpad.net',
67
        'dev': 'launchpad.dev',
68
        }
69
3211.1.1 by Ian Clatworthy
Extends the launchpad plugin's implementation of lp spec urls (Tim Penhey)
70
    # NB: these should always end in a slash to avoid xmlrpclib appending
0.4.7 by Martin Pool
Start making provision to test using a mock xmlrpc transport.
71
    # '/RPC2'
3955.3.2 by Jonathan Lange
Tighten up the code a little, changing the dev service to use https,
72
    LAUNCHPAD_INSTANCE = {}
73
    for instance, domain in LAUNCHPAD_DOMAINS.iteritems():
74
        LAUNCHPAD_INSTANCE[instance] = 'https://xmlrpc.%s/bazaar/' % domain
75
3211.1.1 by Ian Clatworthy
Extends the launchpad plugin's implementation of lp spec urls (Tim Penhey)
76
    # We use edge as the default because:
3200.2.2 by Robert Collins
* The launchpad plugin now uses the ``edge`` xmlrpc server to avoid
77
    # Beta users get redirected to it
78
    # All users can use it
79
    # There is a bug in the launchpad side where redirection causes an OOPS.
3955.3.1 by Jonathan Lange
Start doing URL stuff, extracting the domain bit out of LaunchpadService,
80
    DEFAULT_INSTANCE = 'edge'
81
    DEFAULT_SERVICE_URL = LAUNCHPAD_INSTANCE[DEFAULT_INSTANCE]
0.4.13 by Martin Pool
Update xmlrpc api to pass product name as a parameter.
82
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
83
    transport = None
84
    registrant_email = None
85
    registrant_password = None
86
0.4.29 by Martin Pool
(register-branch) override xmlrpc user-agent; move Transport construction
87
3193.5.1 by Tim Penhey
Mostly working, just need to update the tests for lp://dev
88
    def __init__(self, transport=None, lp_instance=None):
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
89
        """Construct a new service talking to the launchpad rpc server"""
3193.5.1 by Tim Penhey
Mostly working, just need to update the tests for lp://dev
90
        self._lp_instance = lp_instance
0.4.29 by Martin Pool
(register-branch) override xmlrpc user-agent; move Transport construction
91
        if transport is None:
92
            uri_type = urllib.splittype(self.service_url)[0]
93
            if uri_type == 'https':
94
                transport = xmlrpclib.SafeTransport()
95
            else:
96
                transport = xmlrpclib.Transport()
97
            transport.user_agent = 'bzr/%s (xmlrpclib/%s)' \
2900.2.22 by Vincent Ladeuil
Polishing.
98
                    % (_bzrlib_version, xmlrpclib.__version__)
0.4.29 by Martin Pool
(register-branch) override xmlrpc user-agent; move Transport construction
99
        self.transport = transport
100
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
101
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
102
    @property
103
    def service_url(self):
104
        """Return the http or https url for the xmlrpc server.
105
106
        This does not include the username/password credentials.
107
        """
108
        key = 'BZR_LP_XMLRPC_URL'
109
        if key in os.environ:
110
            return os.environ[key]
3193.5.1 by Tim Penhey
Mostly working, just need to update the tests for lp://dev
111
        elif self._lp_instance is not None:
3193.5.2 by Tim Penhey
Updated the tests to handle unknown launchpad instances.
112
            try:
113
                return self.LAUNCHPAD_INSTANCE[self._lp_instance]
114
            except KeyError:
115
                raise InvalidLaunchpadInstance(self._lp_instance)
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
116
        else:
117
            return self.DEFAULT_SERVICE_URL
118
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
119
    def get_proxy(self, authenticated):
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
120
        """Return the proxy for XMLRPC requests."""
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
121
        if authenticated:
122
            # auth info must be in url
123
            # TODO: if there's no registrant email perhaps we should
124
            # just connect anonymously?
125
            scheme, hostinfo, path = urlsplit(self.service_url)[:3]
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
126
            if '@' in hostinfo:
127
                raise AssertionError(hostinfo)
128
            if self.registrant_email is None:
129
                raise AssertionError()
130
            if self.registrant_password is None:
131
                raise AssertionError()
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
132
            # TODO: perhaps fully quote the password to make it very slightly
133
            # obscured
134
            # TODO: can we perhaps add extra Authorization headers
135
            # directly to the request, rather than putting this into
136
            # the url?  perhaps a bit more secure against accidentally
137
            # revealing it.  std66 s3.2.1 discourages putting the
138
            # password in the url.
139
            hostinfo = '%s:%s@%s' % (urllib.quote(self.registrant_email),
140
                                     urllib.quote(self.registrant_password),
141
                                     hostinfo)
142
            url = urlunsplit((scheme, hostinfo, path, '', ''))
143
        else:
144
            url = self.service_url
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
145
        return xmlrpclib.ServerProxy(url, transport=self.transport)
146
147
    def gather_user_credentials(self):
148
        """Get the password from the user."""
2978.5.1 by John Arbash Meinel
Fix bug #162494, 'bzr register-branch' needs proper auth handling.
149
        the_config = config.GlobalConfig()
150
        self.registrant_email = the_config.user_email()
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
151
        if self.registrant_password is None:
2900.2.21 by Vincent Ladeuil
Make lp_registration aware of authentication config.
152
            auth = config.AuthenticationConfig()
153
            scheme, hostinfo = urlsplit(self.service_url)[:2]
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
154
            prompt = 'launchpad.net password for %s: ' % \
155
                    self.registrant_email
2900.2.21 by Vincent Ladeuil
Make lp_registration aware of authentication config.
156
            # We will reuse http[s] credentials if we can, prompt user
157
            # otherwise
158
            self.registrant_password = auth.get_password(scheme, hostinfo,
2978.5.1 by John Arbash Meinel
Fix bug #162494, 'bzr register-branch' needs proper auth handling.
159
                                                         self.registrant_email,
2900.2.21 by Vincent Ladeuil
Make lp_registration aware of authentication config.
160
                                                         prompt=prompt)
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
161
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
162
    def send_request(self, method_name, method_params, authenticated):
163
        proxy = self.get_proxy(authenticated)
0.4.21 by Martin Pool
Refactor BaseRequest.submit so details of submission are in the LaunchpadService
164
        method = getattr(proxy, method_name)
1668.1.9 by Martin Pool
(launchpad plugin) Better reporting of errors from xmlrpc
165
        try:
166
            result = method(*method_params)
167
        except xmlrpclib.ProtocolError, e:
168
            if e.errcode == 301:
169
                # TODO: This can give a ProtocolError representing a 301 error, whose
170
                # e.headers['location'] tells where to go and e.errcode==301; should
171
                # probably log something and retry on the new url.
172
                raise NotImplementedError("should resend request to %s, but this isn't implemented"
173
                        % e.headers.get('Location', 'NO-LOCATION-PRESENT'))
174
            else:
175
                # we don't want to print the original message because its
176
                # str representation includes the plaintext password.
177
                # TODO: print more headers to help in tracking down failures
178
                raise errors.BzrError("xmlrpc protocol error connecting to %s: %s %s"
179
                        % (self.service_url, e.errcode, e.errmsg))
0.4.21 by Martin Pool
Refactor BaseRequest.submit so details of submission are in the LaunchpadService
180
        return result
181
3955.3.1 by Jonathan Lange
Start doing URL stuff, extracting the domain bit out of LaunchpadService,
182
    @property
183
    def domain(self):
184
        if self._lp_instance is None:
185
            instance = self.DEFAULT_INSTANCE
186
        else:
187
            instance = self._lp_instance
188
        return self.LAUNCHPAD_DOMAINS[instance]
189
3955.3.8 by Jonathan Lange
Support lp URL shortcuts.
190
    def get_web_url_from_branch_url(self, branch_url, _request_factory=None):
3955.3.4 by Jonathan Lange
Some error cases, plus a docstring.
191
        """Get the Launchpad web URL for the given branch URL.
192
193
        :raise errors.InvalidURL: if 'branch_url' cannot be identified as a
194
            Launchpad branch URL.
195
        :return: The URL of the branch on Launchpad.
196
        """
3955.3.1 by Jonathan Lange
Start doing URL stuff, extracting the domain bit out of LaunchpadService,
197
        scheme, hostinfo, path = urlsplit(branch_url)[:3]
3955.3.8 by Jonathan Lange
Support lp URL shortcuts.
198
        if _request_factory is None:
199
            _request_factory = ResolveLaunchpadPathRequest
200
        if scheme == 'lp':
201
            resolve = _request_factory(path)
3955.3.9 by Jonathan Lange
Catch errors.
202
            try:
203
                result = resolve.submit(self)
204
            except xmlrpclib.Fault, fault:
205
                raise errors.InvalidURL(branch_url, str(fault))
3955.3.8 by Jonathan Lange
Support lp URL shortcuts.
206
            branch_url = result['urls'][0]
207
            path = urlsplit(branch_url)[2]
208
        else:
209
            domains = (
210
                'bazaar.%s' % domain
211
                for domain in self.LAUNCHPAD_DOMAINS.itervalues())
3955.3.5 by Jonathan Lange
Add an untested plugin, make the error handling a little nicer.
212
            if hostinfo not in domains:
213
                raise NotLaunchpadBranch(branch_url)
3955.3.7 by Jonathan Lange
Test the launchpad-open command. Fix up some minor bugs.
214
        return urlutils.join('https://code.%s' % self.domain, path)
3955.3.1 by Jonathan Lange
Start doing URL stuff, extracting the domain bit out of LaunchpadService,
215
0.4.21 by Martin Pool
Refactor BaseRequest.submit so details of submission are in the LaunchpadService
216
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
217
class BaseRequest(object):
218
    """Base request for talking to a XMLRPC server."""
219
220
    # Set this to the XMLRPC method name.
221
    _methodname = None
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
222
    _authenticated = True
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
223
224
    def _request_params(self):
225
        """Return the arguments to pass to the method"""
226
        raise NotImplementedError(self._request_params)
227
228
    def submit(self, service):
0.4.21 by Martin Pool
Refactor BaseRequest.submit so details of submission are in the LaunchpadService
229
        """Submit request to Launchpad XMLRPC server.
230
231
        :param service: LaunchpadService indicating where to send
232
            the request and the authentication credentials.
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
233
        """
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
234
        return service.send_request(self._methodname, self._request_params(),
235
                                    self._authenticated)
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
236
237
1668.1.12 by Martin Pool
(launchpad plugin) Improved --dry-run that uses a dummy xmlrpc service.
238
class DryRunLaunchpadService(LaunchpadService):
239
    """Service that just absorbs requests without sending to server.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
240
1668.1.12 by Martin Pool
(launchpad plugin) Improved --dry-run that uses a dummy xmlrpc service.
241
    The dummy service does not need authentication.
242
    """
243
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
244
    def send_request(self, method_name, method_params, authenticated):
1668.1.12 by Martin Pool
(launchpad plugin) Improved --dry-run that uses a dummy xmlrpc service.
245
        pass
246
247
    def gather_user_credentials(self):
248
        pass
249
250
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
251
class BranchRegistrationRequest(BaseRequest):
252
    """Request to tell Launchpad about a bzr branch."""
253
254
    _methodname = 'register_branch'
0.4.7 by Martin Pool
Start making provision to test using a mock xmlrpc transport.
255
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
256
    def __init__(self, branch_url,
0.4.15 by Martin Pool
(register-branch) Add command-line options
257
                 branch_name='',
258
                 branch_title='',
259
                 branch_description='',
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
260
                 author_email='',
0.4.15 by Martin Pool
(register-branch) Add command-line options
261
                 product_name='',
262
                 ):
3246.4.3 by Daniel Watkins
Replaced another assert.
263
        if not branch_url:
264
            raise errors.InvalidURL(branch_url, "You need to specify a non-empty branch URL.")
0.4.4 by Martin Pool
Start forming xmlrpc requests
265
        self.branch_url = branch_url
0.4.15 by Martin Pool
(register-branch) Add command-line options
266
        if branch_name:
267
            self.branch_name = branch_name
0.4.14 by Martin Pool
Update xmlrpc api
268
        else:
0.4.15 by Martin Pool
(register-branch) Add command-line options
269
            self.branch_name = self._find_default_branch_name(self.branch_url)
270
        self.branch_title = branch_title
271
        self.branch_description = branch_description
272
        self.author_email = author_email
273
        self.product_name = product_name
0.4.4 by Martin Pool
Start forming xmlrpc requests
274
275
    def _request_params(self):
276
        """Return xmlrpc request parameters"""
0.4.6 by Martin Pool
Put the rest of the parameters into the registration request.
277
        # This must match the parameter tuple expected by Launchpad for this
278
        # method
0.4.4 by Martin Pool
Start forming xmlrpc requests
279
        return (self.branch_url,
0.4.15 by Martin Pool
(register-branch) Add command-line options
280
                self.branch_name,
0.4.14 by Martin Pool
Update xmlrpc api
281
                self.branch_title,
0.4.6 by Martin Pool
Put the rest of the parameters into the registration request.
282
                self.branch_description,
0.4.14 by Martin Pool
Update xmlrpc api
283
                self.author_email,
0.4.13 by Martin Pool
Update xmlrpc api to pass product name as a parameter.
284
                self.product_name,
0.4.4 by Martin Pool
Start forming xmlrpc requests
285
               )
286
0.4.15 by Martin Pool
(register-branch) Add command-line options
287
    def _find_default_branch_name(self, branch_url):
0.4.14 by Martin Pool
Update xmlrpc api
288
        i = branch_url.rfind('/')
289
        return branch_url[i+1:]
290
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
291
292
class BranchBugLinkRequest(BaseRequest):
293
    """Request to link a bzr branch in Launchpad to a bug."""
294
295
    _methodname = 'link_branch_to_bug'
296
297
    def __init__(self, branch_url, bug_id):
0.4.26 by Martin Pool
(register-branch) Add test for link_branch_to_bug and fix its parameters
298
        self.bug_id = bug_id
0.4.19 by test at canonical
add possibility to link to a bug when registering a branch. factor out some common functionality from BranchRegistrationRequest.
299
        self.branch_url = branch_url
300
301
    def _request_params(self):
302
        """Return xmlrpc request parameters"""
303
        # This must match the parameter tuple expected by Launchpad for this
304
        # method
305
        return (self.branch_url, self.bug_id, '')
2898.4.2 by James Henstridge
Add ResolveLaunchpadURLRequest() class to handle lp: URL resolution.
306
307
2898.4.3 by James Henstridge
Make launchpad_transport_indirect() use XMLRPC to resolve the lp: URL.
308
class ResolveLaunchpadPathRequest(BaseRequest):
309
    """Request to resolve the path component of an lp: URL."""
2898.4.2 by James Henstridge
Add ResolveLaunchpadURLRequest() class to handle lp: URL resolution.
310
2898.4.3 by James Henstridge
Make launchpad_transport_indirect() use XMLRPC to resolve the lp: URL.
311
    _methodname = 'resolve_lp_path'
2898.4.2 by James Henstridge
Add ResolveLaunchpadURLRequest() class to handle lp: URL resolution.
312
    _authenticated = False
313
314
    def __init__(self, path):
3246.4.1 by Daniel Watkins
Replaced problematic assertion with exception call.
315
        if not path:
316
            raise errors.InvalidURL(path=path,
317
                                    extra="You must specify a product.")
2898.4.2 by James Henstridge
Add ResolveLaunchpadURLRequest() class to handle lp: URL resolution.
318
        self.path = path
319
320
    def _request_params(self):
321
        """Return xmlrpc request parameters"""
322
        return (self.path,)