/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/plugins/launchpad/lp_registration.py

  • Committer: John Arbash Meinel
  • Date: 2009-06-18 18:18:36 UTC
  • mto: This revision was merged to the branch mainline in revision 4461.
  • Revision ID: john@arbash-meinel.com-20090618181836-biodfkat9a8eyzjz
The new add_inventory_by_delta is returning a CHKInventory when mapping from NULL
Which is completely valid, but 'broke' one of the tests.
So to fix it, changed the test to use CHKInventories on both sides, and add an __eq__
member. The nice thing is that CHKInventory.__eq__ is fairly cheap, since it only
has to check the root keys.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006 Canonical Ltd
 
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
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
 
18
from getpass import getpass
 
19
import os
 
20
from urlparse import urlsplit, urlunsplit
 
21
import urllib
 
22
import xmlrpclib
 
23
 
 
24
from bzrlib.lazy_import import lazy_import
 
25
lazy_import(globals(), """
 
26
from bzrlib import urlutils
 
27
""")
 
28
 
 
29
from bzrlib import (
 
30
    config,
 
31
    errors,
 
32
    __version__ as _bzrlib_version,
 
33
    )
 
34
 
 
35
# for testing, do
 
36
'''
 
37
export BZR_LP_XMLRPC_URL=http://xmlrpc.staging.launchpad.net/bazaar/
 
38
'''
 
39
 
 
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
 
 
48
class NotLaunchpadBranch(errors.BzrError):
 
49
 
 
50
    _fmt = "%(url)s is not registered on Launchpad."
 
51
 
 
52
    def __init__(self, url):
 
53
        errors.BzrError.__init__(self, url=url)
 
54
 
 
55
 
 
56
class LaunchpadService(object):
 
57
    """A service to talk to Launchpad via XMLRPC.
 
58
 
 
59
    See http://bazaar-vcs.org/Specs/LaunchpadRpc for the methods we can call.
 
60
    """
 
61
 
 
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
 
 
70
    # NB: these should always end in a slash to avoid xmlrpclib appending
 
71
    # '/RPC2'
 
72
    LAUNCHPAD_INSTANCE = {}
 
73
    for instance, domain in LAUNCHPAD_DOMAINS.iteritems():
 
74
        LAUNCHPAD_INSTANCE[instance] = 'https://xmlrpc.%s/bazaar/' % domain
 
75
 
 
76
    # We use edge as the default because:
 
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.
 
80
    DEFAULT_INSTANCE = 'edge'
 
81
    DEFAULT_SERVICE_URL = LAUNCHPAD_INSTANCE[DEFAULT_INSTANCE]
 
82
 
 
83
    transport = None
 
84
    registrant_email = None
 
85
    registrant_password = None
 
86
 
 
87
 
 
88
    def __init__(self, transport=None, lp_instance=None):
 
89
        """Construct a new service talking to the launchpad rpc server"""
 
90
        self._lp_instance = lp_instance
 
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)' \
 
98
                    % (_bzrlib_version, xmlrpclib.__version__)
 
99
        self.transport = transport
 
100
 
 
101
 
 
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]
 
111
        elif self._lp_instance is not None:
 
112
            try:
 
113
                return self.LAUNCHPAD_INSTANCE[self._lp_instance]
 
114
            except KeyError:
 
115
                raise InvalidLaunchpadInstance(self._lp_instance)
 
116
        else:
 
117
            return self.DEFAULT_SERVICE_URL
 
118
 
 
119
    def get_proxy(self, authenticated):
 
120
        """Return the proxy for XMLRPC 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]
 
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()
 
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
 
145
        return xmlrpclib.ServerProxy(url, transport=self.transport)
 
146
 
 
147
    def gather_user_credentials(self):
 
148
        """Get the password from the user."""
 
149
        the_config = config.GlobalConfig()
 
150
        self.registrant_email = the_config.user_email()
 
151
        if self.registrant_password is None:
 
152
            auth = config.AuthenticationConfig()
 
153
            scheme, hostinfo = urlsplit(self.service_url)[:2]
 
154
            prompt = 'launchpad.net password for %s: ' % \
 
155
                    self.registrant_email
 
156
            # We will reuse http[s] credentials if we can, prompt user
 
157
            # otherwise
 
158
            self.registrant_password = auth.get_password(scheme, hostinfo,
 
159
                                                         self.registrant_email,
 
160
                                                         prompt=prompt)
 
161
 
 
162
    def send_request(self, method_name, method_params, authenticated):
 
163
        proxy = self.get_proxy(authenticated)
 
164
        method = getattr(proxy, method_name)
 
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))
 
180
        return result
 
181
 
 
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
 
 
190
    def get_web_url_from_branch_url(self, branch_url, _request_factory=None):
 
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
        """
 
197
        scheme, hostinfo, path = urlsplit(branch_url)[:3]
 
198
        if _request_factory is None:
 
199
            _request_factory = ResolveLaunchpadPathRequest
 
200
        if scheme == 'lp':
 
201
            resolve = _request_factory(path)
 
202
            try:
 
203
                result = resolve.submit(self)
 
204
            except xmlrpclib.Fault, fault:
 
205
                raise errors.InvalidURL(branch_url, str(fault))
 
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())
 
212
            if hostinfo not in domains:
 
213
                raise NotLaunchpadBranch(branch_url)
 
214
        return urlutils.join('https://code.%s' % self.domain, path)
 
215
 
 
216
 
 
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
 
222
    _authenticated = True
 
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):
 
229
        """Submit request to Launchpad XMLRPC server.
 
230
 
 
231
        :param service: LaunchpadService indicating where to send
 
232
            the request and the authentication credentials.
 
233
        """
 
234
        return service.send_request(self._methodname, self._request_params(),
 
235
                                    self._authenticated)
 
236
 
 
237
 
 
238
class DryRunLaunchpadService(LaunchpadService):
 
239
    """Service that just absorbs requests without sending to server.
 
240
 
 
241
    The dummy service does not need authentication.
 
242
    """
 
243
 
 
244
    def send_request(self, method_name, method_params, authenticated):
 
245
        pass
 
246
 
 
247
    def gather_user_credentials(self):
 
248
        pass
 
249
 
 
250
 
 
251
class BranchRegistrationRequest(BaseRequest):
 
252
    """Request to tell Launchpad about a bzr branch."""
 
253
 
 
254
    _methodname = 'register_branch'
 
255
 
 
256
    def __init__(self, branch_url,
 
257
                 branch_name='',
 
258
                 branch_title='',
 
259
                 branch_description='',
 
260
                 author_email='',
 
261
                 product_name='',
 
262
                 ):
 
263
        if not branch_url:
 
264
            raise errors.InvalidURL(branch_url, "You need to specify a non-empty branch URL.")
 
265
        self.branch_url = branch_url
 
266
        if branch_name:
 
267
            self.branch_name = branch_name
 
268
        else:
 
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
 
274
 
 
275
    def _request_params(self):
 
276
        """Return xmlrpc request parameters"""
 
277
        # This must match the parameter tuple expected by Launchpad for this
 
278
        # method
 
279
        return (self.branch_url,
 
280
                self.branch_name,
 
281
                self.branch_title,
 
282
                self.branch_description,
 
283
                self.author_email,
 
284
                self.product_name,
 
285
               )
 
286
 
 
287
    def _find_default_branch_name(self, branch_url):
 
288
        i = branch_url.rfind('/')
 
289
        return branch_url[i+1:]
 
290
 
 
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):
 
298
        self.bug_id = bug_id
 
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, '')
 
306
 
 
307
 
 
308
class ResolveLaunchpadPathRequest(BaseRequest):
 
309
    """Request to resolve the path component of an lp: URL."""
 
310
 
 
311
    _methodname = 'resolve_lp_path'
 
312
    _authenticated = False
 
313
 
 
314
    def __init__(self, path):
 
315
        if not path:
 
316
            raise errors.InvalidURL(path=path,
 
317
                                    extra="You must specify a product.")
 
318
        self.path = path
 
319
 
 
320
    def _request_params(self):
 
321
        """Return xmlrpc request parameters"""
 
322
        return (self.path,)