~bzr-pqm/bzr/bzr.dev

0.4.4 by Martin Pool
Start forming xmlrpc requests
1
# Copyright (C) 2006 by 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
1668.1.9 by Martin Pool
(launchpad plugin) Better reporting of errors from xmlrpc
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
0.4.14 by Martin Pool
Update xmlrpc api
24
import bzrlib.config
1668.1.9 by Martin Pool
(launchpad plugin) Better reporting of errors from xmlrpc
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
'''
0.4.13 by Martin Pool
Update xmlrpc api to pass product name as a parameter.
31
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.
32
class LaunchpadService(object):
0.4.27 by Martin Pool
doc
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
    """
0.4.6 by Martin Pool
Put the rest of the parameters into the registration request.
37
0.4.13 by Martin Pool
Update xmlrpc api to pass product name as a parameter.
38
    # NB: this 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.
39
    # '/RPC2'
1668.1.11 by Martin Pool
(launchpad) default xmlrpc service url should be https
40
    DEFAULT_SERVICE_URL = 'https://xmlrpc.launchpad.net/bazaar/'
0.4.13 by Martin Pool
Update xmlrpc api to pass product name as a parameter.
41
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.
42
    transport = None
43
    registrant_email = None
44
    registrant_password = None
45
0.4.29 by Martin Pool
(register-branch) override xmlrpc user-agent; move Transport construction
46
47
    def __init__(self, transport=None):
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
48
        """Construct a new service talking to the launchpad rpc server"""
0.4.29 by Martin Pool
(register-branch) override xmlrpc user-agent; move Transport construction
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
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
59
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.
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
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
75
        # TODO: if there's no registrant email perhaps we should just connect
76
        # anonymously?
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.
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
1668.1.9 by Martin Pool
(launchpad plugin) Better reporting of errors from xmlrpc
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.
0.4.29 by Martin Pool
(register-branch) override xmlrpc user-agent; move Transport construction
87
        hostinfo = '%s:%s@%s' % (urllib.quote(self.registrant_email),
88
                                 urllib.quote(self.registrant_password),
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.
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
0.4.21 by Martin Pool
Refactor BaseRequest.submit so details of submission are in the LaunchpadService
102
    def send_request(self, method_name, method_params):
103
        proxy = self.get_proxy()
104
        assert method_name
105
        method = getattr(proxy, method_name)
1668.1.9 by Martin Pool
(launchpad plugin) Better reporting of errors from xmlrpc
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))
0.4.21 by Martin Pool
Refactor BaseRequest.submit so details of submission are in the LaunchpadService
121
        return result
122
123
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.
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):
0.4.21 by Martin Pool
Refactor BaseRequest.submit so details of submission are in the LaunchpadService
135
        """Submit request to Launchpad XMLRPC server.
136
137
        :param service: LaunchpadService indicating where to send
138
            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.
139
        """
0.4.21 by Martin Pool
Refactor BaseRequest.submit so details of submission are in the LaunchpadService
140
        return service.send_request(self._methodname, self._request_params())
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.
141
142
1668.1.12 by Martin Pool
(launchpad plugin) Improved --dry-run that uses a dummy xmlrpc service.
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
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.
156
class BranchRegistrationRequest(BaseRequest):
157
    """Request to tell Launchpad about a bzr branch."""
158
159
    _methodname = 'register_branch'
0.4.7 by Martin Pool
Start making provision to test using a mock xmlrpc transport.
160
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
161
    def __init__(self, branch_url,
0.4.15 by Martin Pool
(register-branch) Add command-line options
162
                 branch_name='',
163
                 branch_title='',
164
                 branch_description='',
0.4.23 by Martin Pool
(register-branch) fix ordering of parameters and restore transport-level test.
165
                 author_email='',
0.4.15 by Martin Pool
(register-branch) Add command-line options
166
                 product_name='',
167
                 ):
0.4.14 by Martin Pool
Update xmlrpc api
168
        assert branch_url
0.4.4 by Martin Pool
Start forming xmlrpc requests
169
        self.branch_url = branch_url
0.4.15 by Martin Pool
(register-branch) Add command-line options
170
        if branch_name:
171
            self.branch_name = branch_name
0.4.14 by Martin Pool
Update xmlrpc api
172
        else:
0.4.15 by Martin Pool
(register-branch) Add command-line options
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
0.4.4 by Martin Pool
Start forming xmlrpc requests
178
179
    def _request_params(self):
180
        """Return xmlrpc request parameters"""
0.4.6 by Martin Pool
Put the rest of the parameters into the registration request.
181
        # This must match the parameter tuple expected by Launchpad for this
182
        # method
0.4.4 by Martin Pool
Start forming xmlrpc requests
183
        return (self.branch_url,
0.4.15 by Martin Pool
(register-branch) Add command-line options
184
                self.branch_name,
0.4.14 by Martin Pool
Update xmlrpc api
185
                self.branch_title,
0.4.6 by Martin Pool
Put the rest of the parameters into the registration request.
186
                self.branch_description,
0.4.14 by Martin Pool
Update xmlrpc api
187
                self.author_email,
0.4.13 by Martin Pool
Update xmlrpc api to pass product name as a parameter.
188
                self.product_name,
0.4.4 by Martin Pool
Start forming xmlrpc requests
189
               )
190
0.4.15 by Martin Pool
(register-branch) Add command-line options
191
    def _find_default_branch_name(self, branch_url):
0.4.14 by Martin Pool
Update xmlrpc api
192
        i = branch_url.rfind('/')
193
        return branch_url[i+1:]
194
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.
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
0.4.26 by Martin Pool
(register-branch) Add test for link_branch_to_bug and fix its parameters
203
        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.
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, '')