##// END OF EJS Templates
compat: use py3 compatible configparser
marcink -
r405:90bac5eb default
parent child Browse files
Show More
@@ -1,480 +1,480 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2018 RhodeCode 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 os
19 19 import base64
20 20 import locale
21 21 import logging
22 22 import uuid
23 23 import wsgiref.util
24 24 import traceback
25 25 from itertools import chain
26 26
27 27 import simplejson as json
28 28 import msgpack
29 29 from beaker.cache import CacheManager
30 30 from beaker.util import parse_cache_config_options
31 31 from pyramid.config import Configurator
32 32 from pyramid.wsgi import wsgiapp
33 from pyramid.compat import configparser
33 34
34 35 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
35 36 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
36 37 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
37 38 from vcsserver.echo_stub.echo_app import EchoApp
38 39 from vcsserver.exceptions import HTTPRepoLocked
39 40 from vcsserver.server import VcsServer
40 41
41 42 try:
42 43 from vcsserver.git import GitFactory, GitRemote
43 44 except ImportError:
44 45 GitFactory = None
45 46 GitRemote = None
46 47
47 48 try:
48 49 from vcsserver.hg import MercurialFactory, HgRemote
49 50 except ImportError:
50 51 MercurialFactory = None
51 52 HgRemote = None
52 53
53 54 try:
54 55 from vcsserver.svn import SubversionFactory, SvnRemote
55 56 except ImportError:
56 57 SubversionFactory = None
57 58 SvnRemote = None
58 59
59 60 log = logging.getLogger(__name__)
60 61
61 62
62 63 def _is_request_chunked(environ):
63 64 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
64 65 return stream
65 66
66 67
67 68 class VCS(object):
68 69 def __init__(self, locale=None, cache_config=None):
69 70 self.locale = locale
70 71 self.cache_config = cache_config
71 72 self._configure_locale()
72 73 self._initialize_cache()
73 74
74 75 if GitFactory and GitRemote:
75 76 git_repo_cache = self.cache.get_cache_region(
76 77 'git', region='repo_object')
77 78 git_factory = GitFactory(git_repo_cache)
78 79 self._git_remote = GitRemote(git_factory)
79 80 else:
80 81 log.info("Git client import failed")
81 82
82 83 if MercurialFactory and HgRemote:
83 84 hg_repo_cache = self.cache.get_cache_region(
84 85 'hg', region='repo_object')
85 86 hg_factory = MercurialFactory(hg_repo_cache)
86 87 self._hg_remote = HgRemote(hg_factory)
87 88 else:
88 89 log.info("Mercurial client import failed")
89 90
90 91 if SubversionFactory and SvnRemote:
91 92 svn_repo_cache = self.cache.get_cache_region(
92 93 'svn', region='repo_object')
93 94 svn_factory = SubversionFactory(svn_repo_cache)
94 95 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
95 96 else:
96 97 log.info("Subversion client import failed")
97 98
98 99 self._vcsserver = VcsServer()
99 100
100 101 def _initialize_cache(self):
101 102 cache_config = parse_cache_config_options(self.cache_config)
102 103 log.info('Initializing beaker cache: %s' % cache_config)
103 104 self.cache = CacheManager(**cache_config)
104 105
105 106 def _configure_locale(self):
106 107 if self.locale:
107 108 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
108 109 else:
109 110 log.info(
110 111 'Configuring locale subsystem based on environment variables')
111 112 try:
112 113 # If self.locale is the empty string, then the locale
113 114 # module will use the environment variables. See the
114 115 # documentation of the package `locale`.
115 116 locale.setlocale(locale.LC_ALL, self.locale)
116 117
117 118 language_code, encoding = locale.getlocale()
118 119 log.info(
119 120 'Locale set to language code "%s" with encoding "%s".',
120 121 language_code, encoding)
121 122 except locale.Error:
122 123 log.exception(
123 124 'Cannot set locale, not configuring the locale system')
124 125
125 126
126 127 class WsgiProxy(object):
127 128 def __init__(self, wsgi):
128 129 self.wsgi = wsgi
129 130
130 131 def __call__(self, environ, start_response):
131 132 input_data = environ['wsgi.input'].read()
132 133 input_data = msgpack.unpackb(input_data)
133 134
134 135 error = None
135 136 try:
136 137 data, status, headers = self.wsgi.handle(
137 138 input_data['environment'], input_data['input_data'],
138 139 *input_data['args'], **input_data['kwargs'])
139 140 except Exception as e:
140 141 data, status, headers = [], None, None
141 142 error = {
142 143 'message': str(e),
143 144 '_vcs_kind': getattr(e, '_vcs_kind', None)
144 145 }
145 146
146 147 start_response(200, {})
147 148 return self._iterator(error, status, headers, data)
148 149
149 150 def _iterator(self, error, status, headers, data):
150 151 initial_data = [
151 152 error,
152 153 status,
153 154 headers,
154 155 ]
155 156
156 157 for d in chain(initial_data, data):
157 158 yield msgpack.packb(d)
158 159
159 160
160 161 class HTTPApplication(object):
161 162 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
162 163
163 164 remote_wsgi = remote_wsgi
164 165 _use_echo_app = False
165 166
166 167 def __init__(self, settings=None, global_config=None):
167 168 self.config = Configurator(settings=settings)
168 169 self.global_config = global_config
169 170
170 171 locale = settings.get('locale', '') or 'en_US.UTF-8'
171 172 vcs = VCS(locale=locale, cache_config=settings)
172 173 self._remotes = {
173 174 'hg': vcs._hg_remote,
174 175 'git': vcs._git_remote,
175 176 'svn': vcs._svn_remote,
176 177 'server': vcs._vcsserver,
177 178 }
178 179 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
179 180 self._use_echo_app = True
180 181 log.warning("Using EchoApp for VCS operations.")
181 182 self.remote_wsgi = remote_wsgi_stub
182 183 self._configure_settings(settings)
183 184 self._configure()
184 185
185 186 def _configure_settings(self, app_settings):
186 187 """
187 188 Configure the settings module.
188 189 """
189 190 git_path = app_settings.get('git_path', None)
190 191 if git_path:
191 192 settings.GIT_EXECUTABLE = git_path
192 193
193 194 def _configure(self):
194 195 self.config.add_renderer(
195 196 name='msgpack',
196 197 factory=self._msgpack_renderer_factory)
197 198
198 199 self.config.add_route('service', '/_service')
199 200 self.config.add_route('status', '/status')
200 201 self.config.add_route('hg_proxy', '/proxy/hg')
201 202 self.config.add_route('git_proxy', '/proxy/git')
202 203 self.config.add_route('vcs', '/{backend}')
203 204 self.config.add_route('stream_git', '/stream/git/*repo_name')
204 205 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
205 206
206 207 self.config.add_view(
207 208 self.status_view, route_name='status', renderer='json')
208 209 self.config.add_view(
209 210 self.service_view, route_name='service', renderer='msgpack')
210 211
211 212 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
212 213 self.config.add_view(self.git_proxy(), route_name='git_proxy')
213 214 self.config.add_view(
214 215 self.vcs_view, route_name='vcs', renderer='msgpack',
215 216 custom_predicates=[self.is_vcs_view])
216 217
217 218 self.config.add_view(self.hg_stream(), route_name='stream_hg')
218 219 self.config.add_view(self.git_stream(), route_name='stream_git')
219 220
220 221 def notfound(request):
221 222 return {'status': '404 NOT FOUND'}
222 223 self.config.add_notfound_view(notfound, renderer='json')
223 224
224 225 self.config.add_view(self.handle_vcs_exception, context=Exception)
225 226
226 227 self.config.add_tween(
227 228 'vcsserver.tweens.RequestWrapperTween',
228 229 )
229 230
230 231 def wsgi_app(self):
231 232 return self.config.make_wsgi_app()
232 233
233 234 def vcs_view(self, request):
234 235 remote = self._remotes[request.matchdict['backend']]
235 236 payload = msgpack.unpackb(request.body, use_list=True)
236 237 method = payload.get('method')
237 238 params = payload.get('params')
238 239 wire = params.get('wire')
239 240 args = params.get('args')
240 241 kwargs = params.get('kwargs')
241 242 if wire:
242 243 try:
243 244 wire['context'] = uuid.UUID(wire['context'])
244 245 except KeyError:
245 246 pass
246 247 args.insert(0, wire)
247 248
248 249 log.debug('method called:%s with kwargs:%s', method, kwargs)
249 250 try:
250 251 resp = getattr(remote, method)(*args, **kwargs)
251 252 except Exception as e:
252 253 tb_info = traceback.format_exc()
253 254
254 255 type_ = e.__class__.__name__
255 256 if type_ not in self.ALLOWED_EXCEPTIONS:
256 257 type_ = None
257 258
258 259 resp = {
259 260 'id': payload.get('id'),
260 261 'error': {
261 262 'message': e.message,
262 263 'traceback': tb_info,
263 264 'type': type_
264 265 }
265 266 }
266 267 try:
267 268 resp['error']['_vcs_kind'] = e._vcs_kind
268 269 except AttributeError:
269 270 pass
270 271 else:
271 272 resp = {
272 273 'id': payload.get('id'),
273 274 'result': resp
274 275 }
275 276
276 277 return resp
277 278
278 279 def status_view(self, request):
279 280 import vcsserver
280 281 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
281 282 'pid': os.getpid()}
282 283
283 284 def service_view(self, request):
284 285 import vcsserver
285 import ConfigParser as configparser
286 286
287 287 payload = msgpack.unpackb(request.body, use_list=True)
288 288
289 289 try:
290 290 path = self.global_config['__file__']
291 291 config = configparser.ConfigParser()
292 292 config.read(path)
293 293 parsed_ini = config
294 294 if parsed_ini.has_section('server:main'):
295 295 parsed_ini = dict(parsed_ini.items('server:main'))
296 296 except Exception:
297 297 log.exception('Failed to read .ini file for display')
298 298 parsed_ini = {}
299 299
300 300 resp = {
301 301 'id': payload.get('id'),
302 302 'result': dict(
303 303 version=vcsserver.__version__,
304 304 config=parsed_ini,
305 305 payload=payload,
306 306 )
307 307 }
308 308 return resp
309 309
310 310 def _msgpack_renderer_factory(self, info):
311 311 def _render(value, system):
312 312 value = msgpack.packb(value)
313 313 request = system.get('request')
314 314 if request is not None:
315 315 response = request.response
316 316 ct = response.content_type
317 317 if ct == response.default_content_type:
318 318 response.content_type = 'application/x-msgpack'
319 319 return value
320 320 return _render
321 321
322 322 def set_env_from_config(self, environ, config):
323 323 dict_conf = {}
324 324 try:
325 325 for elem in config:
326 326 if elem[0] == 'rhodecode':
327 327 dict_conf = json.loads(elem[2])
328 328 break
329 329 except Exception:
330 330 log.exception('Failed to fetch SCM CONFIG')
331 331 return
332 332
333 333 username = dict_conf.get('username')
334 334 if username:
335 335 environ['REMOTE_USER'] = username
336 336 # mercurial specific, some extension api rely on this
337 337 environ['HGUSER'] = username
338 338
339 339 ip = dict_conf.get('ip')
340 340 if ip:
341 341 environ['REMOTE_HOST'] = ip
342 342
343 343 if _is_request_chunked(environ):
344 344 # set the compatibility flag for webob
345 345 environ['wsgi.input_terminated'] = True
346 346
347 347 def hg_proxy(self):
348 348 @wsgiapp
349 349 def _hg_proxy(environ, start_response):
350 350 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
351 351 return app(environ, start_response)
352 352 return _hg_proxy
353 353
354 354 def git_proxy(self):
355 355 @wsgiapp
356 356 def _git_proxy(environ, start_response):
357 357 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
358 358 return app(environ, start_response)
359 359 return _git_proxy
360 360
361 361 def hg_stream(self):
362 362 if self._use_echo_app:
363 363 @wsgiapp
364 364 def _hg_stream(environ, start_response):
365 365 app = EchoApp('fake_path', 'fake_name', None)
366 366 return app(environ, start_response)
367 367 return _hg_stream
368 368 else:
369 369 @wsgiapp
370 370 def _hg_stream(environ, start_response):
371 371 log.debug('http-app: handling hg stream')
372 372 repo_path = environ['HTTP_X_RC_REPO_PATH']
373 373 repo_name = environ['HTTP_X_RC_REPO_NAME']
374 374 packed_config = base64.b64decode(
375 375 environ['HTTP_X_RC_REPO_CONFIG'])
376 376 config = msgpack.unpackb(packed_config)
377 377 app = scm_app.create_hg_wsgi_app(
378 378 repo_path, repo_name, config)
379 379
380 380 # Consistent path information for hgweb
381 381 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
382 382 environ['REPO_NAME'] = repo_name
383 383 self.set_env_from_config(environ, config)
384 384
385 385 log.debug('http-app: starting app handler '
386 386 'with %s and process request', app)
387 387 return app(environ, ResponseFilter(start_response))
388 388 return _hg_stream
389 389
390 390 def git_stream(self):
391 391 if self._use_echo_app:
392 392 @wsgiapp
393 393 def _git_stream(environ, start_response):
394 394 app = EchoApp('fake_path', 'fake_name', None)
395 395 return app(environ, start_response)
396 396 return _git_stream
397 397 else:
398 398 @wsgiapp
399 399 def _git_stream(environ, start_response):
400 400 log.debug('http-app: handling git stream')
401 401 repo_path = environ['HTTP_X_RC_REPO_PATH']
402 402 repo_name = environ['HTTP_X_RC_REPO_NAME']
403 403 packed_config = base64.b64decode(
404 404 environ['HTTP_X_RC_REPO_CONFIG'])
405 405 config = msgpack.unpackb(packed_config)
406 406
407 407 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
408 408 self.set_env_from_config(environ, config)
409 409
410 410 content_type = environ.get('CONTENT_TYPE', '')
411 411
412 412 path = environ['PATH_INFO']
413 413 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
414 414 log.debug(
415 415 'LFS: Detecting if request `%s` is LFS server path based '
416 416 'on content type:`%s`, is_lfs:%s',
417 417 path, content_type, is_lfs_request)
418 418
419 419 if not is_lfs_request:
420 420 # fallback detection by path
421 421 if GIT_LFS_PROTO_PAT.match(path):
422 422 is_lfs_request = True
423 423 log.debug(
424 424 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
425 425 path, is_lfs_request)
426 426
427 427 if is_lfs_request:
428 428 app = scm_app.create_git_lfs_wsgi_app(
429 429 repo_path, repo_name, config)
430 430 else:
431 431 app = scm_app.create_git_wsgi_app(
432 432 repo_path, repo_name, config)
433 433
434 434 log.debug('http-app: starting app handler '
435 435 'with %s and process request', app)
436 436
437 437 return app(environ, start_response)
438 438
439 439 return _git_stream
440 440
441 441 def is_vcs_view(self, context, request):
442 442 """
443 443 View predicate that returns true if given backend is supported by
444 444 defined remotes.
445 445 """
446 446 backend = request.matchdict.get('backend')
447 447 return backend in self._remotes
448 448
449 449 def handle_vcs_exception(self, exception, request):
450 450 _vcs_kind = getattr(exception, '_vcs_kind', '')
451 451 if _vcs_kind == 'repo_locked':
452 452 # Get custom repo-locked status code if present.
453 453 status_code = request.headers.get('X-RC-Locked-Status-Code')
454 454 return HTTPRepoLocked(
455 455 title=exception.message, status_code=status_code)
456 456
457 457 # Re-raise exception if we can not handle it.
458 458 log.exception(
459 459 'error occurred handling this request for path: %s', request.path)
460 460 raise exception
461 461
462 462
463 463 class ResponseFilter(object):
464 464
465 465 def __init__(self, start_response):
466 466 self._start_response = start_response
467 467
468 468 def __call__(self, status, response_headers, exc_info=None):
469 469 headers = tuple(
470 470 (h, v) for h, v in response_headers
471 471 if not wsgiref.util.is_hop_by_hop(h))
472 472 return self._start_response(status, headers, exc_info)
473 473
474 474
475 475 def main(global_config, **settings):
476 476 if MercurialFactory:
477 477 hgpatches.patch_largefiles_capabilities()
478 478 hgpatches.patch_subrepo_type_mapping()
479 479 app = HTTPApplication(settings=settings, global_config=global_config)
480 480 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now