~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

Exclude more files from dumb-rsync upload

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
 
import bzrlib.config
25
 
import bzrlib.errors as errors
26
 
 
27
 
# for testing, do
28
 
'''
29
 
export BZR_LP_XMLRPC_URL=http://xmlrpc.staging.launchpad.net/bazaar/
30
 
'''
31
 
 
32
 
class LaunchpadService(object):
33
 
    """A service to talk to Launchpad via XMLRPC.
34
 
    
35
 
    See http://bazaar-vcs.org/Specs/LaunchpadRpc for the methods we can call.
36
 
    """
37
 
 
38
 
    # NB: this should always end in a slash to avoid xmlrpclib appending
39
 
    # '/RPC2'
40
 
    DEFAULT_SERVICE_URL = 'https://xmlrpc.launchpad.net/bazaar/'
41
 
 
42
 
    transport = None
43
 
    registrant_email = None
44
 
    registrant_password = None
45
 
 
46
 
 
47
 
    def __init__(self, transport=None):
48
 
        """Construct a new service talking to the launchpad rpc server"""
49
 
        if transport is None:
50
 
            uri_type = urllib.splittype(self.service_url)[0]
51
 
            if uri_type == 'https':
52
 
                transport = xmlrpclib.SafeTransport()
53
 
            else:
54
 
                transport = xmlrpclib.Transport()
55
 
            transport.user_agent = 'bzr/%s (xmlrpclib/%s)' \
56
 
                    % (bzrlib.__version__, xmlrpclib.__version__)
57
 
        self.transport = transport
58
 
 
59
 
 
60
 
    @property
61
 
    def service_url(self):
62
 
        """Return the http or https url for the xmlrpc server.
63
 
 
64
 
        This does not include the username/password credentials.
65
 
        """
66
 
        key = 'BZR_LP_XMLRPC_URL'
67
 
        if key in os.environ:
68
 
            return os.environ[key]
69
 
        else:
70
 
            return self.DEFAULT_SERVICE_URL
71
 
 
72
 
    def get_proxy(self):
73
 
        """Return the proxy for XMLRPC requests."""
74
 
        # auth info must be in url
75
 
        # TODO: if there's no registrant email perhaps we should just connect
76
 
        # anonymously?
77
 
        scheme, hostinfo, path = urlsplit(self.service_url)[:3]
78
 
        assert '@' not in hostinfo
79
 
        assert self.registrant_email is not None
80
 
        assert self.registrant_password is not None
81
 
        # TODO: perhaps fully quote the password to make it very slightly
82
 
        # obscured
83
 
        # TODO: can we perhaps add extra Authorization headers directly to the 
84
 
        # request, rather than putting this into the url?  perhaps a bit more 
85
 
        # secure against accidentally revealing it.  std66 s3.2.1 discourages putting
86
 
        # the password in the url.
87
 
        hostinfo = '%s:%s@%s' % (urllib.quote(self.registrant_email),
88
 
                                 urllib.quote(self.registrant_password),
89
 
                                 hostinfo)
90
 
        url = urlunsplit((scheme, hostinfo, path, '', ''))
91
 
        return xmlrpclib.ServerProxy(url, transport=self.transport)
92
 
 
93
 
    def gather_user_credentials(self):
94
 
        """Get the password from the user."""
95
 
        config = bzrlib.config.GlobalConfig()
96
 
        self.registrant_email = config.user_email()
97
 
        if self.registrant_password is None:
98
 
            prompt = 'launchpad.net password for %s: ' % \
99
 
                    self.registrant_email
100
 
            self.registrant_password = getpass(prompt)
101
 
 
102
 
    def send_request(self, method_name, method_params):
103
 
        proxy = self.get_proxy()
104
 
        assert method_name
105
 
        method = getattr(proxy, method_name)
106
 
        try:
107
 
            result = method(*method_params)
108
 
        except xmlrpclib.ProtocolError, e:
109
 
            if e.errcode == 301:
110
 
                # TODO: This can give a ProtocolError representing a 301 error, whose
111
 
                # e.headers['location'] tells where to go and e.errcode==301; should
112
 
                # probably log something and retry on the new url.
113
 
                raise NotImplementedError("should resend request to %s, but this isn't implemented"
114
 
                        % e.headers.get('Location', 'NO-LOCATION-PRESENT'))
115
 
            else:
116
 
                # we don't want to print the original message because its
117
 
                # str representation includes the plaintext password.
118
 
                # TODO: print more headers to help in tracking down failures
119
 
                raise errors.BzrError("xmlrpc protocol error connecting to %s: %s %s"
120
 
                        % (self.service_url, e.errcode, e.errmsg))
121
 
        return result
122
 
 
123
 
 
124
 
class BaseRequest(object):
125
 
    """Base request for talking to a XMLRPC server."""
126
 
 
127
 
    # Set this to the XMLRPC method name.
128
 
    _methodname = None
129
 
 
130
 
    def _request_params(self):
131
 
        """Return the arguments to pass to the method"""
132
 
        raise NotImplementedError(self._request_params)
133
 
 
134
 
    def submit(self, service):
135
 
        """Submit request to Launchpad XMLRPC server.
136
 
 
137
 
        :param service: LaunchpadService indicating where to send
138
 
            the request and the authentication credentials.
139
 
        """
140
 
        return service.send_request(self._methodname, self._request_params())
141
 
 
142
 
 
143
 
class DryRunLaunchpadService(LaunchpadService):
144
 
    """Service that just absorbs requests without sending to server.
145
 
    
146
 
    The dummy service does not need authentication.
147
 
    """
148
 
 
149
 
    def send_request(self, method_name, method_params):
150
 
        pass
151
 
 
152
 
    def gather_user_credentials(self):
153
 
        pass
154
 
 
155
 
 
156
 
class BranchRegistrationRequest(BaseRequest):
157
 
    """Request to tell Launchpad about a bzr branch."""
158
 
 
159
 
    _methodname = 'register_branch'
160
 
 
161
 
    def __init__(self, branch_url,
162
 
                 branch_name='',
163
 
                 branch_title='',
164
 
                 branch_description='',
165
 
                 author_email='',
166
 
                 product_name='',
167
 
                 ):
168
 
        assert branch_url
169
 
        self.branch_url = branch_url
170
 
        if branch_name:
171
 
            self.branch_name = branch_name
172
 
        else:
173
 
            self.branch_name = self._find_default_branch_name(self.branch_url)
174
 
        self.branch_title = branch_title
175
 
        self.branch_description = branch_description
176
 
        self.author_email = author_email
177
 
        self.product_name = product_name
178
 
 
179
 
    def _request_params(self):
180
 
        """Return xmlrpc request parameters"""
181
 
        # This must match the parameter tuple expected by Launchpad for this
182
 
        # method
183
 
        return (self.branch_url,
184
 
                self.branch_name,
185
 
                self.branch_title,
186
 
                self.branch_description,
187
 
                self.author_email,
188
 
                self.product_name,
189
 
               )
190
 
 
191
 
    def _find_default_branch_name(self, branch_url):
192
 
        i = branch_url.rfind('/')
193
 
        return branch_url[i+1:]
194
 
 
195
 
 
196
 
class BranchBugLinkRequest(BaseRequest):
197
 
    """Request to link a bzr branch in Launchpad to a bug."""
198
 
 
199
 
    _methodname = 'link_branch_to_bug'
200
 
 
201
 
    def __init__(self, branch_url, bug_id):
202
 
        assert branch_url
203
 
        self.bug_id = bug_id
204
 
        self.branch_url = branch_url
205
 
 
206
 
    def _request_params(self):
207
 
        """Return xmlrpc request parameters"""
208
 
        # This must match the parameter tuple expected by Launchpad for this
209
 
        # method
210
 
        return (self.branch_url, self.bug_id, '')