##// END OF EJS Templates
service-endpoint: expose additional data of vcsserver via service-data endpoint....
marcink -
r173:d7a24340 default
parent child Browse files
Show More
@@ -1,408 +1,424 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2017 RodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import base64
19 19 import locale
20 20 import logging
21 21 import uuid
22 22 import wsgiref.util
23 23 import traceback
24 24 from itertools import chain
25 25
26 26 import msgpack
27 27 from beaker.cache import CacheManager
28 28 from beaker.util import parse_cache_config_options
29 29 from pyramid.config import Configurator
30 30 from pyramid.wsgi import wsgiapp
31 31
32 32 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
33 33 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
34 34 from vcsserver.echo_stub.echo_app import EchoApp
35 35 from vcsserver.exceptions import HTTPRepoLocked
36 36 from vcsserver.server import VcsServer
37 37
38 38 try:
39 39 from vcsserver.git import GitFactory, GitRemote
40 40 except ImportError:
41 41 GitFactory = None
42 42 GitRemote = None
43 43 try:
44 44 from vcsserver.hg import MercurialFactory, HgRemote
45 45 except ImportError:
46 46 MercurialFactory = None
47 47 HgRemote = None
48 48 try:
49 49 from vcsserver.svn import SubversionFactory, SvnRemote
50 50 except ImportError:
51 51 SubversionFactory = None
52 52 SvnRemote = None
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 class VCS(object):
58 58 def __init__(self, locale=None, cache_config=None):
59 59 self.locale = locale
60 60 self.cache_config = cache_config
61 61 self._configure_locale()
62 62 self._initialize_cache()
63 63
64 64 if GitFactory and GitRemote:
65 65 git_repo_cache = self.cache.get_cache_region(
66 66 'git', region='repo_object')
67 67 git_factory = GitFactory(git_repo_cache)
68 68 self._git_remote = GitRemote(git_factory)
69 69 else:
70 70 log.info("Git client import failed")
71 71
72 72 if MercurialFactory and HgRemote:
73 73 hg_repo_cache = self.cache.get_cache_region(
74 74 'hg', region='repo_object')
75 75 hg_factory = MercurialFactory(hg_repo_cache)
76 76 self._hg_remote = HgRemote(hg_factory)
77 77 else:
78 78 log.info("Mercurial client import failed")
79 79
80 80 if SubversionFactory and SvnRemote:
81 81 svn_repo_cache = self.cache.get_cache_region(
82 82 'svn', region='repo_object')
83 83 svn_factory = SubversionFactory(svn_repo_cache)
84 84 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
85 85 else:
86 86 log.info("Subversion client import failed")
87 87
88 88 self._vcsserver = VcsServer()
89 89
90 90 def _initialize_cache(self):
91 91 cache_config = parse_cache_config_options(self.cache_config)
92 92 log.info('Initializing beaker cache: %s' % cache_config)
93 93 self.cache = CacheManager(**cache_config)
94 94
95 95 def _configure_locale(self):
96 96 if self.locale:
97 97 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
98 98 else:
99 99 log.info(
100 100 'Configuring locale subsystem based on environment variables')
101 101 try:
102 102 # If self.locale is the empty string, then the locale
103 103 # module will use the environment variables. See the
104 104 # documentation of the package `locale`.
105 105 locale.setlocale(locale.LC_ALL, self.locale)
106 106
107 107 language_code, encoding = locale.getlocale()
108 108 log.info(
109 109 'Locale set to language code "%s" with encoding "%s".',
110 110 language_code, encoding)
111 111 except locale.Error:
112 112 log.exception(
113 113 'Cannot set locale, not configuring the locale system')
114 114
115 115
116 116 class WsgiProxy(object):
117 117 def __init__(self, wsgi):
118 118 self.wsgi = wsgi
119 119
120 120 def __call__(self, environ, start_response):
121 121 input_data = environ['wsgi.input'].read()
122 122 input_data = msgpack.unpackb(input_data)
123 123
124 124 error = None
125 125 try:
126 126 data, status, headers = self.wsgi.handle(
127 127 input_data['environment'], input_data['input_data'],
128 128 *input_data['args'], **input_data['kwargs'])
129 129 except Exception as e:
130 130 data, status, headers = [], None, None
131 131 error = {
132 132 'message': str(e),
133 133 '_vcs_kind': getattr(e, '_vcs_kind', None)
134 134 }
135 135
136 136 start_response(200, {})
137 137 return self._iterator(error, status, headers, data)
138 138
139 139 def _iterator(self, error, status, headers, data):
140 140 initial_data = [
141 141 error,
142 142 status,
143 143 headers,
144 144 ]
145 145
146 146 for d in chain(initial_data, data):
147 147 yield msgpack.packb(d)
148 148
149 149
150 150 class HTTPApplication(object):
151 151 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
152 152
153 153 remote_wsgi = remote_wsgi
154 154 _use_echo_app = False
155 155
156 def __init__(self, settings=None):
156 def __init__(self, settings=None, global_config=None):
157 157 self.config = Configurator(settings=settings)
158 self.global_config = global_config
159
158 160 locale = settings.get('locale', '') or 'en_US.UTF-8'
159 161 vcs = VCS(locale=locale, cache_config=settings)
160 162 self._remotes = {
161 163 'hg': vcs._hg_remote,
162 164 'git': vcs._git_remote,
163 165 'svn': vcs._svn_remote,
164 166 'server': vcs._vcsserver,
165 167 }
166 168 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
167 169 self._use_echo_app = True
168 170 log.warning("Using EchoApp for VCS operations.")
169 171 self.remote_wsgi = remote_wsgi_stub
170 172 self._configure_settings(settings)
171 173 self._configure()
172 174
173 175 def _configure_settings(self, app_settings):
174 176 """
175 177 Configure the settings module.
176 178 """
177 179 git_path = app_settings.get('git_path', None)
178 180 if git_path:
179 181 settings.GIT_EXECUTABLE = git_path
180 182
181 183 def _configure(self):
182 184 self.config.add_renderer(
183 185 name='msgpack',
184 186 factory=self._msgpack_renderer_factory)
185 187
186 188 self.config.add_route('service', '/_service')
187 189 self.config.add_route('status', '/status')
188 190 self.config.add_route('hg_proxy', '/proxy/hg')
189 191 self.config.add_route('git_proxy', '/proxy/git')
190 192 self.config.add_route('vcs', '/{backend}')
191 193 self.config.add_route('stream_git', '/stream/git/*repo_name')
192 194 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
193 195
194 196 self.config.add_view(
195 197 self.status_view, route_name='status', renderer='json')
196 198 self.config.add_view(
197 199 self.service_view, route_name='service', renderer='msgpack')
198 200
199 201 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
200 202 self.config.add_view(self.git_proxy(), route_name='git_proxy')
201 203 self.config.add_view(
202 204 self.vcs_view, route_name='vcs', renderer='msgpack',
203 205 custom_predicates=[self.is_vcs_view])
204 206
205 207 self.config.add_view(self.hg_stream(), route_name='stream_hg')
206 208 self.config.add_view(self.git_stream(), route_name='stream_git')
207 209
208 210 def notfound(request):
209 211 return {'status': '404 NOT FOUND'}
210 212 self.config.add_notfound_view(notfound, renderer='json')
211 213
212 214 self.config.add_view(
213 215 self.handle_vcs_exception, context=Exception,
214 216 custom_predicates=[self.is_vcs_exception])
215 217
216 218 self.config.add_view(
217 219 self.general_error_handler, context=Exception)
218 220
219 221 self.config.add_tween(
220 222 'vcsserver.tweens.RequestWrapperTween',
221 223 )
222 224
223 225 def wsgi_app(self):
224 226 return self.config.make_wsgi_app()
225 227
226 228 def vcs_view(self, request):
227 229 remote = self._remotes[request.matchdict['backend']]
228 230 payload = msgpack.unpackb(request.body, use_list=True)
229 231 method = payload.get('method')
230 232 params = payload.get('params')
231 233 wire = params.get('wire')
232 234 args = params.get('args')
233 235 kwargs = params.get('kwargs')
234 236 if wire:
235 237 try:
236 238 wire['context'] = uuid.UUID(wire['context'])
237 239 except KeyError:
238 240 pass
239 241 args.insert(0, wire)
240 242
241 243 log.debug('method called:%s with kwargs:%s', method, kwargs)
242 244 try:
243 245 resp = getattr(remote, method)(*args, **kwargs)
244 246 except Exception as e:
245 247 tb_info = traceback.format_exc()
246 248
247 249 type_ = e.__class__.__name__
248 250 if type_ not in self.ALLOWED_EXCEPTIONS:
249 251 type_ = None
250 252
251 253 resp = {
252 254 'id': payload.get('id'),
253 255 'error': {
254 256 'message': e.message,
255 257 'traceback': tb_info,
256 258 'type': type_
257 259 }
258 260 }
259 261 try:
260 262 resp['error']['_vcs_kind'] = e._vcs_kind
261 263 except AttributeError:
262 264 pass
263 265 else:
264 266 resp = {
265 267 'id': payload.get('id'),
266 268 'result': resp
267 269 }
268 270
269 271 return resp
270 272
271 273 def status_view(self, request):
272 274 return {'status': 'OK'}
273 275
274 276 def service_view(self, request):
275 277 import vcsserver
278 import ConfigParser as configparser
279
276 280 payload = msgpack.unpackb(request.body, use_list=True)
281
282 try:
283 path = self.global_config['__file__']
284 config = configparser.ConfigParser()
285 config.read(path)
286 parsed_ini = config
287 if parsed_ini.has_section('server:main'):
288 parsed_ini = dict(parsed_ini.items('server:main'))
289 except Exception:
290 log.exception('Failed to read .ini file for display')
291 parsed_ini = {}
292
277 293 resp = {
278 294 'id': payload.get('id'),
279 295 'result': dict(
280 296 version=vcsserver.__version__,
281 config={},
297 config=parsed_ini,
282 298 payload=payload,
283 299 )
284 300 }
285 301 return resp
286 302
287 303 def _msgpack_renderer_factory(self, info):
288 304 def _render(value, system):
289 305 value = msgpack.packb(value)
290 306 request = system.get('request')
291 307 if request is not None:
292 308 response = request.response
293 309 ct = response.content_type
294 310 if ct == response.default_content_type:
295 311 response.content_type = 'application/x-msgpack'
296 312 return value
297 313 return _render
298 314
299 315 def hg_proxy(self):
300 316 @wsgiapp
301 317 def _hg_proxy(environ, start_response):
302 318 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
303 319 return app(environ, start_response)
304 320 return _hg_proxy
305 321
306 322 def git_proxy(self):
307 323 @wsgiapp
308 324 def _git_proxy(environ, start_response):
309 325 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
310 326 return app(environ, start_response)
311 327 return _git_proxy
312 328
313 329 def hg_stream(self):
314 330 if self._use_echo_app:
315 331 @wsgiapp
316 332 def _hg_stream(environ, start_response):
317 333 app = EchoApp('fake_path', 'fake_name', None)
318 334 return app(environ, start_response)
319 335 return _hg_stream
320 336 else:
321 337 @wsgiapp
322 338 def _hg_stream(environ, start_response):
323 339 repo_path = environ['HTTP_X_RC_REPO_PATH']
324 340 repo_name = environ['HTTP_X_RC_REPO_NAME']
325 341 packed_config = base64.b64decode(
326 342 environ['HTTP_X_RC_REPO_CONFIG'])
327 343 config = msgpack.unpackb(packed_config)
328 344 app = scm_app.create_hg_wsgi_app(
329 345 repo_path, repo_name, config)
330 346
331 347 # Consitent path information for hgweb
332 348 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
333 349 environ['REPO_NAME'] = repo_name
334 350 return app(environ, ResponseFilter(start_response))
335 351 return _hg_stream
336 352
337 353 def git_stream(self):
338 354 if self._use_echo_app:
339 355 @wsgiapp
340 356 def _git_stream(environ, start_response):
341 357 app = EchoApp('fake_path', 'fake_name', None)
342 358 return app(environ, start_response)
343 359 return _git_stream
344 360 else:
345 361 @wsgiapp
346 362 def _git_stream(environ, start_response):
347 363 repo_path = environ['HTTP_X_RC_REPO_PATH']
348 364 repo_name = environ['HTTP_X_RC_REPO_NAME']
349 365 packed_config = base64.b64decode(
350 366 environ['HTTP_X_RC_REPO_CONFIG'])
351 367 config = msgpack.unpackb(packed_config)
352 368
353 369 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
354 370 app = scm_app.create_git_wsgi_app(
355 371 repo_path, repo_name, config)
356 372 return app(environ, start_response)
357 373 return _git_stream
358 374
359 375 def is_vcs_view(self, context, request):
360 376 """
361 377 View predicate that returns true if given backend is supported by
362 378 defined remotes.
363 379 """
364 380 backend = request.matchdict.get('backend')
365 381 return backend in self._remotes
366 382
367 383 def is_vcs_exception(self, context, request):
368 384 """
369 385 View predicate that returns true if the context object is a VCS
370 386 exception.
371 387 """
372 388 return hasattr(context, '_vcs_kind')
373 389
374 390 def handle_vcs_exception(self, exception, request):
375 391 if exception._vcs_kind == 'repo_locked':
376 392 # Get custom repo-locked status code if present.
377 393 status_code = request.headers.get('X-RC-Locked-Status-Code')
378 394 return HTTPRepoLocked(
379 395 title=exception.message, status_code=status_code)
380 396
381 397 # Re-raise exception if we can not handle it.
382 398 raise exception
383 399
384 400 def general_error_handler(self, exception, request):
385 401 log.exception(
386 402 'error occurred handling this request for path: %s',
387 403 request.path)
388 404 raise exception
389 405
390 406
391 407 class ResponseFilter(object):
392 408
393 409 def __init__(self, start_response):
394 410 self._start_response = start_response
395 411
396 412 def __call__(self, status, response_headers, exc_info=None):
397 413 headers = tuple(
398 414 (h, v) for h, v in response_headers
399 415 if not wsgiref.util.is_hop_by_hop(h))
400 416 return self._start_response(status, headers, exc_info)
401 417
402 418
403 419 def main(global_config, **settings):
404 420 if MercurialFactory:
405 421 hgpatches.patch_largefiles_capabilities()
406 422 hgpatches.patch_subrepo_type_mapping()
407 app = HTTPApplication(settings=settings)
423 app = HTTPApplication(settings=settings, global_config=global_config)
408 424 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now