/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: 2008-08-18 22:34:21 UTC
  • mto: (3606.5.6 1.6)
  • mto: This revision was merged to the branch mainline in revision 3641.
  • Revision ID: john@arbash-meinel.com-20080818223421-todjny24vj4faj4t
Add tests for the fetching behavior.

The proper parameter passed is 'unordered' add an assert for it, and
fix callers that were passing 'unsorted' instead.
Add tests that we make the right get_record_stream call based
on the value of _fetch_uses_deltas.
Fix the fetch request for signatures.

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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 import (
 
25
    config,
 
26
    errors,
 
27
    __version__ as _bzrlib_version,
 
28
    )
 
29
 
 
30
# for testing, do
 
31
'''
 
32
export BZR_LP_XMLRPC_URL=http://xmlrpc.staging.launchpad.net/bazaar/
 
33
'''
 
34
 
 
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
 
 
43
class LaunchpadService(object):
 
44
    """A service to talk to Launchpad via XMLRPC.
 
45
 
 
46
    See http://bazaar-vcs.org/Specs/LaunchpadRpc for the methods we can call.
 
47
    """
 
48
 
 
49
    # NB: these should always end in a slash to avoid xmlrpclib appending
 
50
    # '/RPC2'
 
51
    # We use edge as the default because:
 
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.
 
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
        }
 
62
    DEFAULT_SERVICE_URL = LAUNCHPAD_INSTANCE['edge']
 
63
 
 
64
    transport = None
 
65
    registrant_email = None
 
66
    registrant_password = None
 
67
 
 
68
 
 
69
    def __init__(self, transport=None, lp_instance=None):
 
70
        """Construct a new service talking to the launchpad rpc server"""
 
71
        self._lp_instance = lp_instance
 
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)' \
 
79
                    % (_bzrlib_version, xmlrpclib.__version__)
 
80
        self.transport = transport
 
81
 
 
82
 
 
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]
 
92
        elif self._lp_instance is not None:
 
93
            try:
 
94
                return self.LAUNCHPAD_INSTANCE[self._lp_instance]
 
95
            except KeyError:
 
96
                raise InvalidLaunchpadInstance(self._lp_instance)
 
97
        else:
 
98
            return self.DEFAULT_SERVICE_URL
 
99
 
 
100
    def get_proxy(self, authenticated):
 
101
        """Return the proxy for XMLRPC 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]
 
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()
 
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
 
126
        return xmlrpclib.ServerProxy(url, transport=self.transport)
 
127
 
 
128
    def gather_user_credentials(self):
 
129
        """Get the password from the user."""
 
130
        the_config = config.GlobalConfig()
 
131
        self.registrant_email = the_config.user_email()
 
132
        if self.registrant_password is None:
 
133
            auth = config.AuthenticationConfig()
 
134
            scheme, hostinfo = urlsplit(self.service_url)[:2]
 
135
            prompt = 'launchpad.net password for %s: ' % \
 
136
                    self.registrant_email
 
137
            # We will reuse http[s] credentials if we can, prompt user
 
138
            # otherwise
 
139
            self.registrant_password = auth.get_password(scheme, hostinfo,
 
140
                                                         self.registrant_email,
 
141
                                                         prompt=prompt)
 
142
 
 
143
    def send_request(self, method_name, method_params, authenticated):
 
144
        proxy = self.get_proxy(authenticated)
 
145
        method = getattr(proxy, method_name)
 
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))
 
161
        return result
 
162
 
 
163
 
 
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
 
169
    _authenticated = True
 
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):
 
176
        """Submit request to Launchpad XMLRPC server.
 
177
 
 
178
        :param service: LaunchpadService indicating where to send
 
179
            the request and the authentication credentials.
 
180
        """
 
181
        return service.send_request(self._methodname, self._request_params(),
 
182
                                    self._authenticated)
 
183
 
 
184
 
 
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
 
 
191
    def send_request(self, method_name, method_params, authenticated):
 
192
        pass
 
193
 
 
194
    def gather_user_credentials(self):
 
195
        pass
 
196
 
 
197
 
 
198
class BranchRegistrationRequest(BaseRequest):
 
199
    """Request to tell Launchpad about a bzr branch."""
 
200
 
 
201
    _methodname = 'register_branch'
 
202
 
 
203
    def __init__(self, branch_url,
 
204
                 branch_name='',
 
205
                 branch_title='',
 
206
                 branch_description='',
 
207
                 author_email='',
 
208
                 product_name='',
 
209
                 ):
 
210
        if not branch_url:
 
211
            raise errors.InvalidURL(branch_url, "You need to specify a non-empty branch URL.")
 
212
        self.branch_url = branch_url
 
213
        if branch_name:
 
214
            self.branch_name = branch_name
 
215
        else:
 
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
 
221
 
 
222
    def _request_params(self):
 
223
        """Return xmlrpc request parameters"""
 
224
        # This must match the parameter tuple expected by Launchpad for this
 
225
        # method
 
226
        return (self.branch_url,
 
227
                self.branch_name,
 
228
                self.branch_title,
 
229
                self.branch_description,
 
230
                self.author_email,
 
231
                self.product_name,
 
232
               )
 
233
 
 
234
    def _find_default_branch_name(self, branch_url):
 
235
        i = branch_url.rfind('/')
 
236
        return branch_url[i+1:]
 
237
 
 
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):
 
245
        self.bug_id = bug_id
 
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, '')
 
253
 
 
254
 
 
255
class ResolveLaunchpadPathRequest(BaseRequest):
 
256
    """Request to resolve the path component of an lp: URL."""
 
257
 
 
258
    _methodname = 'resolve_lp_path'
 
259
    _authenticated = False
 
260
 
 
261
    def __init__(self, path):
 
262
        if not path:
 
263
            raise errors.InvalidURL(path=path,
 
264
                                    extra="You must specify a product.")
 
265
        self.path = path
 
266
 
 
267
    def _request_params(self):
 
268
        """Return xmlrpc request parameters"""
 
269
        return (self.path,)