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