##// END OF EJS Templates
logging: skip large attribute expansion on archive_repo. We don't need arguments there
marcink -
r742:45bacab2 default
parent child Browse files
Show More
@@ -1,611 +1,620 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2019 RhodeCode GmbH
2 # Copyright (C) 2014-2019 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import os
18 import os
19 import sys
19 import sys
20 import base64
20 import base64
21 import locale
21 import locale
22 import logging
22 import logging
23 import uuid
23 import uuid
24 import wsgiref.util
24 import wsgiref.util
25 import traceback
25 import traceback
26 import tempfile
26 import tempfile
27 from itertools import chain
27 from itertools import chain
28
28
29 import simplejson as json
29 import simplejson as json
30 import msgpack
30 import msgpack
31 from pyramid.config import Configurator
31 from pyramid.config import Configurator
32 from pyramid.settings import asbool, aslist
32 from pyramid.settings import asbool, aslist
33 from pyramid.wsgi import wsgiapp
33 from pyramid.wsgi import wsgiapp
34 from pyramid.compat import configparser
34 from pyramid.compat import configparser
35
35
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
39 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
40 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
40 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
41
41
42 try:
42 try:
43 locale.setlocale(locale.LC_ALL, '')
43 locale.setlocale(locale.LC_ALL, '')
44 except locale.Error as e:
44 except locale.Error as e:
45 log.error(
45 log.error(
46 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
46 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
47 os.environ['LC_ALL'] = 'C'
47 os.environ['LC_ALL'] = 'C'
48
48
49 import vcsserver
49 import vcsserver
50 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
50 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
51 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
51 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
52 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
52 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
53 from vcsserver.echo_stub.echo_app import EchoApp
53 from vcsserver.echo_stub.echo_app import EchoApp
54 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
54 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
55 from vcsserver.lib.exc_tracking import store_exception
55 from vcsserver.lib.exc_tracking import store_exception
56 from vcsserver.server import VcsServer
56 from vcsserver.server import VcsServer
57
57
58 try:
58 try:
59 from vcsserver.git import GitFactory, GitRemote
59 from vcsserver.git import GitFactory, GitRemote
60 except ImportError:
60 except ImportError:
61 GitFactory = None
61 GitFactory = None
62 GitRemote = None
62 GitRemote = None
63
63
64 try:
64 try:
65 from vcsserver.hg import MercurialFactory, HgRemote
65 from vcsserver.hg import MercurialFactory, HgRemote
66 except ImportError:
66 except ImportError:
67 MercurialFactory = None
67 MercurialFactory = None
68 HgRemote = None
68 HgRemote = None
69
69
70 try:
70 try:
71 from vcsserver.svn import SubversionFactory, SvnRemote
71 from vcsserver.svn import SubversionFactory, SvnRemote
72 except ImportError:
72 except ImportError:
73 SubversionFactory = None
73 SubversionFactory = None
74 SvnRemote = None
74 SvnRemote = None
75
75
76
76
77 def _is_request_chunked(environ):
77 def _is_request_chunked(environ):
78 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
78 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
79 return stream
79 return stream
80
80
81
81
82 def _int_setting(settings, name, default):
82 def _int_setting(settings, name, default):
83 settings[name] = int(settings.get(name, default))
83 settings[name] = int(settings.get(name, default))
84 return settings[name]
84 return settings[name]
85
85
86
86
87 def _bool_setting(settings, name, default):
87 def _bool_setting(settings, name, default):
88 input_val = settings.get(name, default)
88 input_val = settings.get(name, default)
89 if isinstance(input_val, unicode):
89 if isinstance(input_val, unicode):
90 input_val = input_val.encode('utf8')
90 input_val = input_val.encode('utf8')
91 settings[name] = asbool(input_val)
91 settings[name] = asbool(input_val)
92 return settings[name]
92 return settings[name]
93
93
94
94
95 def _list_setting(settings, name, default):
95 def _list_setting(settings, name, default):
96 raw_value = settings.get(name, default)
96 raw_value = settings.get(name, default)
97
97
98 # Otherwise we assume it uses pyramids space/newline separation.
98 # Otherwise we assume it uses pyramids space/newline separation.
99 settings[name] = aslist(raw_value)
99 settings[name] = aslist(raw_value)
100 return settings[name]
100 return settings[name]
101
101
102
102
103 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
103 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
104 value = settings.get(name, default)
104 value = settings.get(name, default)
105
105
106 if default_when_empty and not value:
106 if default_when_empty and not value:
107 # use default value when value is empty
107 # use default value when value is empty
108 value = default
108 value = default
109
109
110 if lower:
110 if lower:
111 value = value.lower()
111 value = value.lower()
112 settings[name] = value
112 settings[name] = value
113 return settings[name]
113 return settings[name]
114
114
115
115
116 class VCS(object):
116 class VCS(object):
117 def __init__(self, locale=None, cache_config=None):
117 def __init__(self, locale=None, cache_config=None):
118 self.locale = locale
118 self.locale = locale
119 self.cache_config = cache_config
119 self.cache_config = cache_config
120 self._configure_locale()
120 self._configure_locale()
121
121
122 if GitFactory and GitRemote:
122 if GitFactory and GitRemote:
123 git_factory = GitFactory()
123 git_factory = GitFactory()
124 self._git_remote = GitRemote(git_factory)
124 self._git_remote = GitRemote(git_factory)
125 else:
125 else:
126 log.info("Git client import failed")
126 log.info("Git client import failed")
127
127
128 if MercurialFactory and HgRemote:
128 if MercurialFactory and HgRemote:
129 hg_factory = MercurialFactory()
129 hg_factory = MercurialFactory()
130 self._hg_remote = HgRemote(hg_factory)
130 self._hg_remote = HgRemote(hg_factory)
131 else:
131 else:
132 log.info("Mercurial client import failed")
132 log.info("Mercurial client import failed")
133
133
134 if SubversionFactory and SvnRemote:
134 if SubversionFactory and SvnRemote:
135 svn_factory = SubversionFactory()
135 svn_factory = SubversionFactory()
136
136
137 # hg factory is used for svn url validation
137 # hg factory is used for svn url validation
138 hg_factory = MercurialFactory()
138 hg_factory = MercurialFactory()
139 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
139 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
140 else:
140 else:
141 log.info("Subversion client import failed")
141 log.info("Subversion client import failed")
142
142
143 self._vcsserver = VcsServer()
143 self._vcsserver = VcsServer()
144
144
145 def _configure_locale(self):
145 def _configure_locale(self):
146 if self.locale:
146 if self.locale:
147 log.info('Settings locale: `LC_ALL` to %s', self.locale)
147 log.info('Settings locale: `LC_ALL` to %s', self.locale)
148 else:
148 else:
149 log.info(
149 log.info(
150 'Configuring locale subsystem based on environment variables')
150 'Configuring locale subsystem based on environment variables')
151 try:
151 try:
152 # If self.locale is the empty string, then the locale
152 # If self.locale is the empty string, then the locale
153 # module will use the environment variables. See the
153 # module will use the environment variables. See the
154 # documentation of the package `locale`.
154 # documentation of the package `locale`.
155 locale.setlocale(locale.LC_ALL, self.locale)
155 locale.setlocale(locale.LC_ALL, self.locale)
156
156
157 language_code, encoding = locale.getlocale()
157 language_code, encoding = locale.getlocale()
158 log.info(
158 log.info(
159 'Locale set to language code "%s" with encoding "%s".',
159 'Locale set to language code "%s" with encoding "%s".',
160 language_code, encoding)
160 language_code, encoding)
161 except locale.Error:
161 except locale.Error:
162 log.exception(
162 log.exception(
163 'Cannot set locale, not configuring the locale system')
163 'Cannot set locale, not configuring the locale system')
164
164
165
165
166 class WsgiProxy(object):
166 class WsgiProxy(object):
167 def __init__(self, wsgi):
167 def __init__(self, wsgi):
168 self.wsgi = wsgi
168 self.wsgi = wsgi
169
169
170 def __call__(self, environ, start_response):
170 def __call__(self, environ, start_response):
171 input_data = environ['wsgi.input'].read()
171 input_data = environ['wsgi.input'].read()
172 input_data = msgpack.unpackb(input_data)
172 input_data = msgpack.unpackb(input_data)
173
173
174 error = None
174 error = None
175 try:
175 try:
176 data, status, headers = self.wsgi.handle(
176 data, status, headers = self.wsgi.handle(
177 input_data['environment'], input_data['input_data'],
177 input_data['environment'], input_data['input_data'],
178 *input_data['args'], **input_data['kwargs'])
178 *input_data['args'], **input_data['kwargs'])
179 except Exception as e:
179 except Exception as e:
180 data, status, headers = [], None, None
180 data, status, headers = [], None, None
181 error = {
181 error = {
182 'message': str(e),
182 'message': str(e),
183 '_vcs_kind': getattr(e, '_vcs_kind', None)
183 '_vcs_kind': getattr(e, '_vcs_kind', None)
184 }
184 }
185
185
186 start_response(200, {})
186 start_response(200, {})
187 return self._iterator(error, status, headers, data)
187 return self._iterator(error, status, headers, data)
188
188
189 def _iterator(self, error, status, headers, data):
189 def _iterator(self, error, status, headers, data):
190 initial_data = [
190 initial_data = [
191 error,
191 error,
192 status,
192 status,
193 headers,
193 headers,
194 ]
194 ]
195
195
196 for d in chain(initial_data, data):
196 for d in chain(initial_data, data):
197 yield msgpack.packb(d)
197 yield msgpack.packb(d)
198
198
199
199
200 def not_found(request):
200 def not_found(request):
201 return {'status': '404 NOT FOUND'}
201 return {'status': '404 NOT FOUND'}
202
202
203
203
204 class VCSViewPredicate(object):
204 class VCSViewPredicate(object):
205 def __init__(self, val, config):
205 def __init__(self, val, config):
206 self.remotes = val
206 self.remotes = val
207
207
208 def text(self):
208 def text(self):
209 return 'vcs view method = %s' % (self.remotes.keys(),)
209 return 'vcs view method = %s' % (self.remotes.keys(),)
210
210
211 phash = text
211 phash = text
212
212
213 def __call__(self, context, request):
213 def __call__(self, context, request):
214 """
214 """
215 View predicate that returns true if given backend is supported by
215 View predicate that returns true if given backend is supported by
216 defined remotes.
216 defined remotes.
217 """
217 """
218 backend = request.matchdict.get('backend')
218 backend = request.matchdict.get('backend')
219 return backend in self.remotes
219 return backend in self.remotes
220
220
221
221
222 class HTTPApplication(object):
222 class HTTPApplication(object):
223 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
223 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
224
224
225 remote_wsgi = remote_wsgi
225 remote_wsgi = remote_wsgi
226 _use_echo_app = False
226 _use_echo_app = False
227
227
228 def __init__(self, settings=None, global_config=None):
228 def __init__(self, settings=None, global_config=None):
229 self._sanitize_settings_and_apply_defaults(settings)
229 self._sanitize_settings_and_apply_defaults(settings)
230
230
231 self.config = Configurator(settings=settings)
231 self.config = Configurator(settings=settings)
232 self.global_config = global_config
232 self.global_config = global_config
233 self.config.include('vcsserver.lib.rc_cache')
233 self.config.include('vcsserver.lib.rc_cache')
234
234
235 locale = settings.get('locale', '') or 'en_US.UTF-8'
235 locale = settings.get('locale', '') or 'en_US.UTF-8'
236 vcs = VCS(locale=locale, cache_config=settings)
236 vcs = VCS(locale=locale, cache_config=settings)
237 self._remotes = {
237 self._remotes = {
238 'hg': vcs._hg_remote,
238 'hg': vcs._hg_remote,
239 'git': vcs._git_remote,
239 'git': vcs._git_remote,
240 'svn': vcs._svn_remote,
240 'svn': vcs._svn_remote,
241 'server': vcs._vcsserver,
241 'server': vcs._vcsserver,
242 }
242 }
243 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
243 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
244 self._use_echo_app = True
244 self._use_echo_app = True
245 log.warning("Using EchoApp for VCS operations.")
245 log.warning("Using EchoApp for VCS operations.")
246 self.remote_wsgi = remote_wsgi_stub
246 self.remote_wsgi = remote_wsgi_stub
247
247
248 self._configure_settings(global_config, settings)
248 self._configure_settings(global_config, settings)
249 self._configure()
249 self._configure()
250
250
251 def _configure_settings(self, global_config, app_settings):
251 def _configure_settings(self, global_config, app_settings):
252 """
252 """
253 Configure the settings module.
253 Configure the settings module.
254 """
254 """
255 settings_merged = global_config.copy()
255 settings_merged = global_config.copy()
256 settings_merged.update(app_settings)
256 settings_merged.update(app_settings)
257
257
258 git_path = app_settings.get('git_path', None)
258 git_path = app_settings.get('git_path', None)
259 if git_path:
259 if git_path:
260 settings.GIT_EXECUTABLE = git_path
260 settings.GIT_EXECUTABLE = git_path
261 binary_dir = app_settings.get('core.binary_dir', None)
261 binary_dir = app_settings.get('core.binary_dir', None)
262 if binary_dir:
262 if binary_dir:
263 settings.BINARY_DIR = binary_dir
263 settings.BINARY_DIR = binary_dir
264
264
265 # Store the settings to make them available to other modules.
265 # Store the settings to make them available to other modules.
266 vcsserver.PYRAMID_SETTINGS = settings_merged
266 vcsserver.PYRAMID_SETTINGS = settings_merged
267 vcsserver.CONFIG = settings_merged
267 vcsserver.CONFIG = settings_merged
268
268
269 def _sanitize_settings_and_apply_defaults(self, settings):
269 def _sanitize_settings_and_apply_defaults(self, settings):
270 temp_store = tempfile.gettempdir()
270 temp_store = tempfile.gettempdir()
271 default_cache_dir = os.path.join(temp_store, 'rc_cache')
271 default_cache_dir = os.path.join(temp_store, 'rc_cache')
272
272
273 # save default, cache dir, and use it for all backends later.
273 # save default, cache dir, and use it for all backends later.
274 default_cache_dir = _string_setting(
274 default_cache_dir = _string_setting(
275 settings,
275 settings,
276 'cache_dir',
276 'cache_dir',
277 default_cache_dir, lower=False, default_when_empty=True)
277 default_cache_dir, lower=False, default_when_empty=True)
278
278
279 # ensure we have our dir created
279 # ensure we have our dir created
280 if not os.path.isdir(default_cache_dir):
280 if not os.path.isdir(default_cache_dir):
281 os.makedirs(default_cache_dir, mode=0o755)
281 os.makedirs(default_cache_dir, mode=0o755)
282
282
283 # exception store cache
283 # exception store cache
284 _string_setting(
284 _string_setting(
285 settings,
285 settings,
286 'exception_tracker.store_path',
286 'exception_tracker.store_path',
287 temp_store, lower=False, default_when_empty=True)
287 temp_store, lower=False, default_when_empty=True)
288
288
289 # repo_object cache
289 # repo_object cache
290 _string_setting(
290 _string_setting(
291 settings,
291 settings,
292 'rc_cache.repo_object.backend',
292 'rc_cache.repo_object.backend',
293 'dogpile.cache.rc.memory_lru')
293 'dogpile.cache.rc.memory_lru')
294 _int_setting(
294 _int_setting(
295 settings,
295 settings,
296 'rc_cache.repo_object.expiration_time',
296 'rc_cache.repo_object.expiration_time',
297 300)
297 300)
298 _int_setting(
298 _int_setting(
299 settings,
299 settings,
300 'rc_cache.repo_object.max_size',
300 'rc_cache.repo_object.max_size',
301 1024)
301 1024)
302
302
303 def _configure(self):
303 def _configure(self):
304 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
304 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
305
305
306 self.config.add_route('service', '/_service')
306 self.config.add_route('service', '/_service')
307 self.config.add_route('status', '/status')
307 self.config.add_route('status', '/status')
308 self.config.add_route('hg_proxy', '/proxy/hg')
308 self.config.add_route('hg_proxy', '/proxy/hg')
309 self.config.add_route('git_proxy', '/proxy/git')
309 self.config.add_route('git_proxy', '/proxy/git')
310 self.config.add_route('vcs', '/{backend}')
310 self.config.add_route('vcs', '/{backend}')
311 self.config.add_route('stream_git', '/stream/git/*repo_name')
311 self.config.add_route('stream_git', '/stream/git/*repo_name')
312 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
312 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
313
313
314 self.config.add_view(self.status_view, route_name='status', renderer='json')
314 self.config.add_view(self.status_view, route_name='status', renderer='json')
315 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
315 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
316
316
317 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
317 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
318 self.config.add_view(self.git_proxy(), route_name='git_proxy')
318 self.config.add_view(self.git_proxy(), route_name='git_proxy')
319 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
319 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
320 vcs_view=self._remotes)
320 vcs_view=self._remotes)
321
321
322 self.config.add_view(self.hg_stream(), route_name='stream_hg')
322 self.config.add_view(self.hg_stream(), route_name='stream_hg')
323 self.config.add_view(self.git_stream(), route_name='stream_git')
323 self.config.add_view(self.git_stream(), route_name='stream_git')
324
324
325 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
325 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
326
326
327 self.config.add_notfound_view(not_found, renderer='json')
327 self.config.add_notfound_view(not_found, renderer='json')
328
328
329 self.config.add_view(self.handle_vcs_exception, context=Exception)
329 self.config.add_view(self.handle_vcs_exception, context=Exception)
330
330
331 self.config.add_tween(
331 self.config.add_tween(
332 'vcsserver.tweens.RequestWrapperTween',
332 'vcsserver.tweens.RequestWrapperTween',
333 )
333 )
334
334
335 def wsgi_app(self):
335 def wsgi_app(self):
336 return self.config.make_wsgi_app()
336 return self.config.make_wsgi_app()
337
337
338 def vcs_view(self, request):
338 def vcs_view(self, request):
339 remote = self._remotes[request.matchdict['backend']]
339 remote = self._remotes[request.matchdict['backend']]
340 payload = msgpack.unpackb(request.body, use_list=True)
340 payload = msgpack.unpackb(request.body, use_list=True)
341 method = payload.get('method')
341 method = payload.get('method')
342 params = payload.get('params')
342 params = payload.get('params')
343 wire = params.get('wire')
343 wire = params.get('wire')
344 args = params.get('args')
344 args = params.get('args')
345 kwargs = params.get('kwargs')
345 kwargs = params.get('kwargs')
346 context_uid = None
346 context_uid = None
347
347
348 if wire:
348 if wire:
349 try:
349 try:
350 wire['context'] = context_uid = uuid.UUID(wire['context'])
350 wire['context'] = context_uid = uuid.UUID(wire['context'])
351 except KeyError:
351 except KeyError:
352 pass
352 pass
353 args.insert(0, wire)
353 args.insert(0, wire)
354
354
355 log.debug('method called:%s with args:%s kwargs:%s context_uid: %s',
355 # NOTE(marcink): trading complexity for slight performance
356 method, args[1:], kwargs, context_uid)
356 if log.isEnabledFor(logging.DEBUG):
357 no_args_methods = [
358 'archive_repo'
359 ]
360 if method in no_args_methods:
361 call_args = ''
362 else:
363 call_args = args[1:]
364 log.debug('method called:%s with args:%s kwargs:%s context_uid: %s',
365 method, call_args, kwargs, context_uid)
357
366
358 try:
367 try:
359 resp = getattr(remote, method)(*args, **kwargs)
368 resp = getattr(remote, method)(*args, **kwargs)
360 except Exception as e:
369 except Exception as e:
361 exc_info = list(sys.exc_info())
370 exc_info = list(sys.exc_info())
362 exc_type, exc_value, exc_traceback = exc_info
371 exc_type, exc_value, exc_traceback = exc_info
363
372
364 org_exc = getattr(e, '_org_exc', None)
373 org_exc = getattr(e, '_org_exc', None)
365 org_exc_name = None
374 org_exc_name = None
366 org_exc_tb = ''
375 org_exc_tb = ''
367 if org_exc:
376 if org_exc:
368 org_exc_name = org_exc.__class__.__name__
377 org_exc_name = org_exc.__class__.__name__
369 org_exc_tb = getattr(e, '_org_exc_tb', '')
378 org_exc_tb = getattr(e, '_org_exc_tb', '')
370 # replace our "faked" exception with our org
379 # replace our "faked" exception with our org
371 exc_info[0] = org_exc.__class__
380 exc_info[0] = org_exc.__class__
372 exc_info[1] = org_exc
381 exc_info[1] = org_exc
373
382
374 store_exception(id(exc_info), exc_info)
383 store_exception(id(exc_info), exc_info)
375
384
376 tb_info = ''.join(
385 tb_info = ''.join(
377 traceback.format_exception(exc_type, exc_value, exc_traceback))
386 traceback.format_exception(exc_type, exc_value, exc_traceback))
378
387
379 type_ = e.__class__.__name__
388 type_ = e.__class__.__name__
380 if type_ not in self.ALLOWED_EXCEPTIONS:
389 if type_ not in self.ALLOWED_EXCEPTIONS:
381 type_ = None
390 type_ = None
382
391
383 resp = {
392 resp = {
384 'id': payload.get('id'),
393 'id': payload.get('id'),
385 'error': {
394 'error': {
386 'message': e.message,
395 'message': e.message,
387 'traceback': tb_info,
396 'traceback': tb_info,
388 'org_exc': org_exc_name,
397 'org_exc': org_exc_name,
389 'org_exc_tb': org_exc_tb,
398 'org_exc_tb': org_exc_tb,
390 'type': type_
399 'type': type_
391 }
400 }
392 }
401 }
393 try:
402 try:
394 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
403 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
395 except AttributeError:
404 except AttributeError:
396 pass
405 pass
397 else:
406 else:
398 resp = {
407 resp = {
399 'id': payload.get('id'),
408 'id': payload.get('id'),
400 'result': resp
409 'result': resp
401 }
410 }
402
411
403 return resp
412 return resp
404
413
405 def status_view(self, request):
414 def status_view(self, request):
406 import vcsserver
415 import vcsserver
407 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
416 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
408 'pid': os.getpid()}
417 'pid': os.getpid()}
409
418
410 def service_view(self, request):
419 def service_view(self, request):
411 import vcsserver
420 import vcsserver
412
421
413 payload = msgpack.unpackb(request.body, use_list=True)
422 payload = msgpack.unpackb(request.body, use_list=True)
414
423
415 try:
424 try:
416 path = self.global_config['__file__']
425 path = self.global_config['__file__']
417 config = configparser.ConfigParser()
426 config = configparser.ConfigParser()
418 config.read(path)
427 config.read(path)
419 parsed_ini = config
428 parsed_ini = config
420 if parsed_ini.has_section('server:main'):
429 if parsed_ini.has_section('server:main'):
421 parsed_ini = dict(parsed_ini.items('server:main'))
430 parsed_ini = dict(parsed_ini.items('server:main'))
422 except Exception:
431 except Exception:
423 log.exception('Failed to read .ini file for display')
432 log.exception('Failed to read .ini file for display')
424 parsed_ini = {}
433 parsed_ini = {}
425
434
426 resp = {
435 resp = {
427 'id': payload.get('id'),
436 'id': payload.get('id'),
428 'result': dict(
437 'result': dict(
429 version=vcsserver.__version__,
438 version=vcsserver.__version__,
430 config=parsed_ini,
439 config=parsed_ini,
431 payload=payload,
440 payload=payload,
432 )
441 )
433 }
442 }
434 return resp
443 return resp
435
444
436 def _msgpack_renderer_factory(self, info):
445 def _msgpack_renderer_factory(self, info):
437 def _render(value, system):
446 def _render(value, system):
438 value = msgpack.packb(value)
447 value = msgpack.packb(value)
439 request = system.get('request')
448 request = system.get('request')
440 if request is not None:
449 if request is not None:
441 response = request.response
450 response = request.response
442 ct = response.content_type
451 ct = response.content_type
443 if ct == response.default_content_type:
452 if ct == response.default_content_type:
444 response.content_type = 'application/x-msgpack'
453 response.content_type = 'application/x-msgpack'
445 return value
454 return value
446 return _render
455 return _render
447
456
448 def set_env_from_config(self, environ, config):
457 def set_env_from_config(self, environ, config):
449 dict_conf = {}
458 dict_conf = {}
450 try:
459 try:
451 for elem in config:
460 for elem in config:
452 if elem[0] == 'rhodecode':
461 if elem[0] == 'rhodecode':
453 dict_conf = json.loads(elem[2])
462 dict_conf = json.loads(elem[2])
454 break
463 break
455 except Exception:
464 except Exception:
456 log.exception('Failed to fetch SCM CONFIG')
465 log.exception('Failed to fetch SCM CONFIG')
457 return
466 return
458
467
459 username = dict_conf.get('username')
468 username = dict_conf.get('username')
460 if username:
469 if username:
461 environ['REMOTE_USER'] = username
470 environ['REMOTE_USER'] = username
462 # mercurial specific, some extension api rely on this
471 # mercurial specific, some extension api rely on this
463 environ['HGUSER'] = username
472 environ['HGUSER'] = username
464
473
465 ip = dict_conf.get('ip')
474 ip = dict_conf.get('ip')
466 if ip:
475 if ip:
467 environ['REMOTE_HOST'] = ip
476 environ['REMOTE_HOST'] = ip
468
477
469 if _is_request_chunked(environ):
478 if _is_request_chunked(environ):
470 # set the compatibility flag for webob
479 # set the compatibility flag for webob
471 environ['wsgi.input_terminated'] = True
480 environ['wsgi.input_terminated'] = True
472
481
473 def hg_proxy(self):
482 def hg_proxy(self):
474 @wsgiapp
483 @wsgiapp
475 def _hg_proxy(environ, start_response):
484 def _hg_proxy(environ, start_response):
476 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
485 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
477 return app(environ, start_response)
486 return app(environ, start_response)
478 return _hg_proxy
487 return _hg_proxy
479
488
480 def git_proxy(self):
489 def git_proxy(self):
481 @wsgiapp
490 @wsgiapp
482 def _git_proxy(environ, start_response):
491 def _git_proxy(environ, start_response):
483 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
492 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
484 return app(environ, start_response)
493 return app(environ, start_response)
485 return _git_proxy
494 return _git_proxy
486
495
487 def hg_stream(self):
496 def hg_stream(self):
488 if self._use_echo_app:
497 if self._use_echo_app:
489 @wsgiapp
498 @wsgiapp
490 def _hg_stream(environ, start_response):
499 def _hg_stream(environ, start_response):
491 app = EchoApp('fake_path', 'fake_name', None)
500 app = EchoApp('fake_path', 'fake_name', None)
492 return app(environ, start_response)
501 return app(environ, start_response)
493 return _hg_stream
502 return _hg_stream
494 else:
503 else:
495 @wsgiapp
504 @wsgiapp
496 def _hg_stream(environ, start_response):
505 def _hg_stream(environ, start_response):
497 log.debug('http-app: handling hg stream')
506 log.debug('http-app: handling hg stream')
498 repo_path = environ['HTTP_X_RC_REPO_PATH']
507 repo_path = environ['HTTP_X_RC_REPO_PATH']
499 repo_name = environ['HTTP_X_RC_REPO_NAME']
508 repo_name = environ['HTTP_X_RC_REPO_NAME']
500 packed_config = base64.b64decode(
509 packed_config = base64.b64decode(
501 environ['HTTP_X_RC_REPO_CONFIG'])
510 environ['HTTP_X_RC_REPO_CONFIG'])
502 config = msgpack.unpackb(packed_config)
511 config = msgpack.unpackb(packed_config)
503 app = scm_app.create_hg_wsgi_app(
512 app = scm_app.create_hg_wsgi_app(
504 repo_path, repo_name, config)
513 repo_path, repo_name, config)
505
514
506 # Consistent path information for hgweb
515 # Consistent path information for hgweb
507 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
516 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
508 environ['REPO_NAME'] = repo_name
517 environ['REPO_NAME'] = repo_name
509 self.set_env_from_config(environ, config)
518 self.set_env_from_config(environ, config)
510
519
511 log.debug('http-app: starting app handler '
520 log.debug('http-app: starting app handler '
512 'with %s and process request', app)
521 'with %s and process request', app)
513 return app(environ, ResponseFilter(start_response))
522 return app(environ, ResponseFilter(start_response))
514 return _hg_stream
523 return _hg_stream
515
524
516 def git_stream(self):
525 def git_stream(self):
517 if self._use_echo_app:
526 if self._use_echo_app:
518 @wsgiapp
527 @wsgiapp
519 def _git_stream(environ, start_response):
528 def _git_stream(environ, start_response):
520 app = EchoApp('fake_path', 'fake_name', None)
529 app = EchoApp('fake_path', 'fake_name', None)
521 return app(environ, start_response)
530 return app(environ, start_response)
522 return _git_stream
531 return _git_stream
523 else:
532 else:
524 @wsgiapp
533 @wsgiapp
525 def _git_stream(environ, start_response):
534 def _git_stream(environ, start_response):
526 log.debug('http-app: handling git stream')
535 log.debug('http-app: handling git stream')
527 repo_path = environ['HTTP_X_RC_REPO_PATH']
536 repo_path = environ['HTTP_X_RC_REPO_PATH']
528 repo_name = environ['HTTP_X_RC_REPO_NAME']
537 repo_name = environ['HTTP_X_RC_REPO_NAME']
529 packed_config = base64.b64decode(
538 packed_config = base64.b64decode(
530 environ['HTTP_X_RC_REPO_CONFIG'])
539 environ['HTTP_X_RC_REPO_CONFIG'])
531 config = msgpack.unpackb(packed_config)
540 config = msgpack.unpackb(packed_config)
532
541
533 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
542 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
534 self.set_env_from_config(environ, config)
543 self.set_env_from_config(environ, config)
535
544
536 content_type = environ.get('CONTENT_TYPE', '')
545 content_type = environ.get('CONTENT_TYPE', '')
537
546
538 path = environ['PATH_INFO']
547 path = environ['PATH_INFO']
539 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
548 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
540 log.debug(
549 log.debug(
541 'LFS: Detecting if request `%s` is LFS server path based '
550 'LFS: Detecting if request `%s` is LFS server path based '
542 'on content type:`%s`, is_lfs:%s',
551 'on content type:`%s`, is_lfs:%s',
543 path, content_type, is_lfs_request)
552 path, content_type, is_lfs_request)
544
553
545 if not is_lfs_request:
554 if not is_lfs_request:
546 # fallback detection by path
555 # fallback detection by path
547 if GIT_LFS_PROTO_PAT.match(path):
556 if GIT_LFS_PROTO_PAT.match(path):
548 is_lfs_request = True
557 is_lfs_request = True
549 log.debug(
558 log.debug(
550 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
559 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
551 path, is_lfs_request)
560 path, is_lfs_request)
552
561
553 if is_lfs_request:
562 if is_lfs_request:
554 app = scm_app.create_git_lfs_wsgi_app(
563 app = scm_app.create_git_lfs_wsgi_app(
555 repo_path, repo_name, config)
564 repo_path, repo_name, config)
556 else:
565 else:
557 app = scm_app.create_git_wsgi_app(
566 app = scm_app.create_git_wsgi_app(
558 repo_path, repo_name, config)
567 repo_path, repo_name, config)
559
568
560 log.debug('http-app: starting app handler '
569 log.debug('http-app: starting app handler '
561 'with %s and process request', app)
570 'with %s and process request', app)
562
571
563 return app(environ, start_response)
572 return app(environ, start_response)
564
573
565 return _git_stream
574 return _git_stream
566
575
567 def handle_vcs_exception(self, exception, request):
576 def handle_vcs_exception(self, exception, request):
568 _vcs_kind = getattr(exception, '_vcs_kind', '')
577 _vcs_kind = getattr(exception, '_vcs_kind', '')
569 if _vcs_kind == 'repo_locked':
578 if _vcs_kind == 'repo_locked':
570 # Get custom repo-locked status code if present.
579 # Get custom repo-locked status code if present.
571 status_code = request.headers.get('X-RC-Locked-Status-Code')
580 status_code = request.headers.get('X-RC-Locked-Status-Code')
572 return HTTPRepoLocked(
581 return HTTPRepoLocked(
573 title=exception.message, status_code=status_code)
582 title=exception.message, status_code=status_code)
574
583
575 elif _vcs_kind == 'repo_branch_protected':
584 elif _vcs_kind == 'repo_branch_protected':
576 # Get custom repo-branch-protected status code if present.
585 # Get custom repo-branch-protected status code if present.
577 return HTTPRepoBranchProtected(title=exception.message)
586 return HTTPRepoBranchProtected(title=exception.message)
578
587
579 exc_info = request.exc_info
588 exc_info = request.exc_info
580 store_exception(id(exc_info), exc_info)
589 store_exception(id(exc_info), exc_info)
581
590
582 traceback_info = 'unavailable'
591 traceback_info = 'unavailable'
583 if request.exc_info:
592 if request.exc_info:
584 exc_type, exc_value, exc_tb = request.exc_info
593 exc_type, exc_value, exc_tb = request.exc_info
585 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
594 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
586
595
587 log.error(
596 log.error(
588 'error occurred handling this request for path: %s, \n tb: %s',
597 'error occurred handling this request for path: %s, \n tb: %s',
589 request.path, traceback_info)
598 request.path, traceback_info)
590 raise exception
599 raise exception
591
600
592
601
593 class ResponseFilter(object):
602 class ResponseFilter(object):
594
603
595 def __init__(self, start_response):
604 def __init__(self, start_response):
596 self._start_response = start_response
605 self._start_response = start_response
597
606
598 def __call__(self, status, response_headers, exc_info=None):
607 def __call__(self, status, response_headers, exc_info=None):
599 headers = tuple(
608 headers = tuple(
600 (h, v) for h, v in response_headers
609 (h, v) for h, v in response_headers
601 if not wsgiref.util.is_hop_by_hop(h))
610 if not wsgiref.util.is_hop_by_hop(h))
602 return self._start_response(status, headers, exc_info)
611 return self._start_response(status, headers, exc_info)
603
612
604
613
605 def main(global_config, **settings):
614 def main(global_config, **settings):
606 if MercurialFactory:
615 if MercurialFactory:
607 hgpatches.patch_largefiles_capabilities()
616 hgpatches.patch_largefiles_capabilities()
608 hgpatches.patch_subrepo_type_mapping()
617 hgpatches.patch_subrepo_type_mapping()
609
618
610 app = HTTPApplication(settings=settings, global_config=global_config)
619 app = HTTPApplication(settings=settings, global_config=global_config)
611 return app.wsgi_app()
620 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now