~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Robert Collins
  • Date: 2007-11-09 17:50:31 UTC
  • mto: This revision was merged to the branch mainline in revision 2988.
  • Revision ID: robertc@robertcollins.net-20071109175031-agaiy6530rvbprmb
Change (without backwards compatibility) the
iter_lines_added_or_present_in_versions VersionedFile API to yield the
text version that each line is being returned from. This is useful for
reconcile in determining what inventories reference what texts.
(Robert Collins)

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