~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
2900.2.21 by Vincent Ladeuil
Make lp_registration aware of authentication config.
24
from bzrlib import (
25
    config,
26
    errors,
2900.2.22 by Vincent Ladeuil
Polishing.
27
    __version__ as _bzrlib_version,
2900.2.21 by Vincent Ladeuil
Make lp_registration aware of authentication config.
28
    )
1668.1.9 by Martin Pool
(launchpad plugin) Better reporting of errors from xmlrpc
29
30
# for testing, do
31
'''
32
export BZR_LP_XMLRPC_URL=http://xmlrpc.staging.launchpad.net/bazaar/
33
'''
0.4.13 by Martin Pool
Update xmlrpc api to pass product name as a parameter.
34
3193.5.2 by Tim Penhey
Updated the tests to handle unknown launchpad instances.
35
class InvalidLaunchpadInstance(errors.BzrError):
36
37
    _fmt = "%(lp_instance)s is not a valid Launchpad instance."
38
39
    def __init__(self, lp_instance):
40
        errors.BzrError.__init__(self, lp_instance=lp_instance)
41
42
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.
43
class LaunchpadService(object):
0.4.27 by Martin Pool
doc
44
    """A service to talk to Launchpad via XMLRPC.
3193.5.2 by Tim Penhey
Updated the tests to handle unknown launchpad instances.
45
0.4.27 by Martin Pool
doc
46
    See http://bazaar-vcs.org/Specs/LaunchpadRpc for the methods we can call.
47
    """
0.4.6 by Martin Pool
Put the rest of the parameters into the registration request.
48
3211.1.1 by Ian Clatworthy
Extends the launchpad plugin's implementation of lp spec urls (Tim Penhey)
49
    # 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.
50
    # '/RPC2'
3211.1.1 by Ian Clatworthy
Extends the launchpad plugin's implementation of lp spec urls (Tim Penhey)
51
    # We use edge as the default because:
3200.2.2 by Robert Collins
* The launchpad plugin now uses the ``edge`` xmlrpc server to avoid
52
    # Beta users get redirected to it
53
    # All users can use it
54
    # There is a bug in the launchpad side where redirection causes an OOPS.
3193.5.1 by Tim Penhey
Mostly working, just need to update the tests for lp://dev
55
    LAUNCHPAD_INSTANCE = {
56
        'production': 'https://xmlrpc.launchpad.net/bazaar/',
57
        'edge': 'https://xmlrpc.edge.launchpad.net/bazaar/',
58
        'staging': 'https://xmlrpc.staging.launchpad.net/bazaar/',
59
        'demo': 'https://xmlrpc.demo.launchpad.net/bazaar/',
60
        'dev': 'http://xmlrpc.launchpad.dev/bazaar/',
61
        }
3211.1.1 by Ian Clatworthy
Extends the launchpad plugin's implementation of lp spec urls (Tim Penhey)
62
    DEFAULT_SERVICE_URL = LAUNCHPAD_INSTANCE['edge']
0.4.13 by Martin Pool
Update xmlrpc api to pass product name as a parameter.
63
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.
64
    transport = None
65
    registrant_email = None
66
    registrant_password = None
67
0.4.29 by Martin Pool
(register-branch) override xmlrpc user-agent; move Transport construction
68
3193.5.1 by Tim Penhey
Mostly working, just need to update the tests for lp://dev
69
    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.
70
        """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
71
        self._lp_instance = lp_instance
0.4.29 by Martin Pool
(register-branch) override xmlrpc user-agent; move Transport construction
72
        if transport is None:
73
            uri_type = urllib.splittype(self.service_url)[0]
74
            if uri_type == 'https':
75
                transport = xmlrpclib.SafeTransport()
76
            else:
77
                transport = xmlrpclib.Transport()
78
            transport.user_agent = 'bzr/%s (xmlrpclib/%s)' \
2900.2.22 by Vincent Ladeuil
Polishing.
79
                    % (_bzrlib_version, xmlrpclib.__version__)
0.4.29 by Martin Pool
(register-branch) override xmlrpc user-agent; move Transport construction
80
        self.transport = transport
81
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
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
    @property
84
    def service_url(self):
85
        """Return the http or https url for the xmlrpc server.
86
87
        This does not include the username/password credentials.
88
        """
89
        key = 'BZR_LP_XMLRPC_URL'
90
        if key in os.environ:
91
            return os.environ[key]
3193.5.1 by Tim Penhey
Mostly working, just need to update the tests for lp://dev
92
        elif self._lp_instance is not None:
3193.5.2 by Tim Penhey
Updated the tests to handle unknown launchpad instances.
93
            try:
94
                return self.LAUNCHPAD_INSTANCE[self._lp_instance]
95
            except KeyError:
96
                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.
97
        else:
98
            return self.DEFAULT_SERVICE_URL
99
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
100
    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.
101
        """Return the proxy for XMLRPC requests."""
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
102
        if authenticated:
103
            # auth info must be in url
104
            # TODO: if there's no registrant email perhaps we should
105
            # just connect anonymously?
106
            scheme, hostinfo, path = urlsplit(self.service_url)[:3]
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
107
            if '@' in hostinfo:
108
                raise AssertionError(hostinfo)
109
            if self.registrant_email is None:
110
                raise AssertionError()
111
            if self.registrant_password is None:
112
                raise AssertionError()
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
113
            # TODO: perhaps fully quote the password to make it very slightly
114
            # obscured
115
            # TODO: can we perhaps add extra Authorization headers
116
            # directly to the request, rather than putting this into
117
            # the url?  perhaps a bit more secure against accidentally
118
            # revealing it.  std66 s3.2.1 discourages putting the
119
            # password in the url.
120
            hostinfo = '%s:%s@%s' % (urllib.quote(self.registrant_email),
121
                                     urllib.quote(self.registrant_password),
122
                                     hostinfo)
123
            url = urlunsplit((scheme, hostinfo, path, '', ''))
124
        else:
125
            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.
126
        return xmlrpclib.ServerProxy(url, transport=self.transport)
127
128
    def gather_user_credentials(self):
129
        """Get the password from the user."""
2978.5.1 by John Arbash Meinel
Fix bug #162494, 'bzr register-branch' needs proper auth handling.
130
        the_config = config.GlobalConfig()
131
        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.
132
        if self.registrant_password is None:
2900.2.21 by Vincent Ladeuil
Make lp_registration aware of authentication config.
133
            auth = config.AuthenticationConfig()
134
            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.
135
            prompt = 'launchpad.net password for %s: ' % \
136
                    self.registrant_email
2900.2.21 by Vincent Ladeuil
Make lp_registration aware of authentication config.
137
            # We will reuse http[s] credentials if we can, prompt user
138
            # otherwise
139
            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.
140
                                                         self.registrant_email,
2900.2.21 by Vincent Ladeuil
Make lp_registration aware of authentication config.
141
                                                         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.
142
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
143
    def send_request(self, method_name, method_params, authenticated):
144
        proxy = self.get_proxy(authenticated)
0.4.21 by Martin Pool
Refactor BaseRequest.submit so details of submission are in the LaunchpadService
145
        method = getattr(proxy, method_name)
1668.1.9 by Martin Pool
(launchpad plugin) Better reporting of errors from xmlrpc
146
        try:
147
            result = method(*method_params)
148
        except xmlrpclib.ProtocolError, e:
149
            if e.errcode == 301:
150
                # TODO: This can give a ProtocolError representing a 301 error, whose
151
                # e.headers['location'] tells where to go and e.errcode==301; should
152
                # probably log something and retry on the new url.
153
                raise NotImplementedError("should resend request to %s, but this isn't implemented"
154
                        % e.headers.get('Location', 'NO-LOCATION-PRESENT'))
155
            else:
156
                # we don't want to print the original message because its
157
                # str representation includes the plaintext password.
158
                # TODO: print more headers to help in tracking down failures
159
                raise errors.BzrError("xmlrpc protocol error connecting to %s: %s %s"
160
                        % (self.service_url, e.errcode, e.errmsg))
0.4.21 by Martin Pool
Refactor BaseRequest.submit so details of submission are in the LaunchpadService
161
        return result
162
163
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.
164
class BaseRequest(object):
165
    """Base request for talking to a XMLRPC server."""
166
167
    # Set this to the XMLRPC method name.
168
    _methodname = None
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
169
    _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.
170
171
    def _request_params(self):
172
        """Return the arguments to pass to the method"""
173
        raise NotImplementedError(self._request_params)
174
175
    def submit(self, service):
0.4.21 by Martin Pool
Refactor BaseRequest.submit so details of submission are in the LaunchpadService
176
        """Submit request to Launchpad XMLRPC server.
177
178
        :param service: LaunchpadService indicating where to send
179
            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.
180
        """
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
181
        return service.send_request(self._methodname, self._request_params(),
182
                                    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.
183
184
1668.1.12 by Martin Pool
(launchpad plugin) Improved --dry-run that uses a dummy xmlrpc service.
185
class DryRunLaunchpadService(LaunchpadService):
186
    """Service that just absorbs requests without sending to server.
187
    
188
    The dummy service does not need authentication.
189
    """
190
2898.4.1 by James Henstridge
Make it possible to make unauthenticated XML-RPC requests.
191
    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.
192
        pass
193
194
    def gather_user_credentials(self):
195
        pass
196
197
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.
198
class BranchRegistrationRequest(BaseRequest):
199
    """Request to tell Launchpad about a bzr branch."""
200
201
    _methodname = 'register_branch'
0.4.7 by Martin Pool
Start making provision to test using a mock xmlrpc transport.
202
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
203
    def __init__(self, branch_url,
0.4.15 by Martin Pool
(register-branch) Add command-line options
204
                 branch_name='',
205
                 branch_title='',
206
                 branch_description='',
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
207
                 author_email='',
0.4.15 by Martin Pool
(register-branch) Add command-line options
208
                 product_name='',
209
                 ):
3246.4.3 by Daniel Watkins
Replaced another assert.
210
        if not branch_url:
211
            raise errors.InvalidURL(branch_url, "You need to specify a non-empty branch URL.")
0.4.4 by Martin Pool
Start forming xmlrpc requests
212
        self.branch_url = branch_url
0.4.15 by Martin Pool
(register-branch) Add command-line options
213
        if branch_name:
214
            self.branch_name = branch_name
0.4.14 by Martin Pool
Update xmlrpc api
215
        else:
0.4.15 by Martin Pool
(register-branch) Add command-line options
216
            self.branch_name = self._find_default_branch_name(self.branch_url)
217
        self.branch_title = branch_title
218
        self.branch_description = branch_description
219
        self.author_email = author_email
220
        self.product_name = product_name
0.4.4 by Martin Pool
Start forming xmlrpc requests
221
222
    def _request_params(self):
223
        """Return xmlrpc request parameters"""
0.4.6 by Martin Pool
Put the rest of the parameters into the registration request.
224
        # This must match the parameter tuple expected by Launchpad for this
225
        # method
0.4.4 by Martin Pool
Start forming xmlrpc requests
226
        return (self.branch_url,
0.4.15 by Martin Pool
(register-branch) Add command-line options
227
                self.branch_name,
0.4.14 by Martin Pool
Update xmlrpc api
228
                self.branch_title,
0.4.6 by Martin Pool
Put the rest of the parameters into the registration request.
229
                self.branch_description,
0.4.14 by Martin Pool
Update xmlrpc api
230
                self.author_email,
0.4.13 by Martin Pool
Update xmlrpc api to pass product name as a parameter.
231
                self.product_name,
0.4.4 by Martin Pool
Start forming xmlrpc requests
232
               )
233
0.4.15 by Martin Pool
(register-branch) Add command-line options
234
    def _find_default_branch_name(self, branch_url):
0.4.14 by Martin Pool
Update xmlrpc api
235
        i = branch_url.rfind('/')
236
        return branch_url[i+1:]
237
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.
238
239
class BranchBugLinkRequest(BaseRequest):
240
    """Request to link a bzr branch in Launchpad to a bug."""
241
242
    _methodname = 'link_branch_to_bug'
243
244
    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
245
        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.
246
        self.branch_url = branch_url
247
248
    def _request_params(self):
249
        """Return xmlrpc request parameters"""
250
        # This must match the parameter tuple expected by Launchpad for this
251
        # method
252
        return (self.branch_url, self.bug_id, '')
2898.4.2 by James Henstridge
Add ResolveLaunchpadURLRequest() class to handle lp: URL resolution.
253
254
2898.4.3 by James Henstridge
Make launchpad_transport_indirect() use XMLRPC to resolve the lp: URL.
255
class ResolveLaunchpadPathRequest(BaseRequest):
256
    """Request to resolve the path component of an lp: URL."""
2898.4.2 by James Henstridge
Add ResolveLaunchpadURLRequest() class to handle lp: URL resolution.
257
2898.4.3 by James Henstridge
Make launchpad_transport_indirect() use XMLRPC to resolve the lp: URL.
258
    _methodname = 'resolve_lp_path'
2898.4.2 by James Henstridge
Add ResolveLaunchpadURLRequest() class to handle lp: URL resolution.
259
    _authenticated = False
260
261
    def __init__(self, path):
3246.4.1 by Daniel Watkins
Replaced problematic assertion with exception call.
262
        if not path:
263
            raise errors.InvalidURL(path=path,
264
                                    extra="You must specify a product.")
2898.4.2 by James Henstridge
Add ResolveLaunchpadURLRequest() class to handle lp: URL resolution.
265
        self.path = path
266
267
    def _request_params(self):
268
        """Return xmlrpc request parameters"""
269
        return (self.path,)