##// END OF EJS Templates
pyramid: don't use deprecated custom_predicates in view config
marcink -
r583:d119c397 default
parent child Browse files
Show More
@@ -1,598 +1,607 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-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 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):
201 return {'status': '404 NOT FOUND'}
202
203
204 class VCSViewPredicate(object):
205 def __init__(self, val, config):
206 self.remotes = val
207
208 def text(self):
209 return 'vcs view method = %s' % (self.remotes.keys(),)
210
211 phash = text
212
213 def __call__(self, context, request):
214 """
215 View predicate that returns true if given backend is supported by
216 defined remotes.
217 """
218 backend = request.matchdict.get('backend')
219 return backend in self.remotes
220
221
200 class HTTPApplication(object):
222 class HTTPApplication(object):
201 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
223 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
202
224
203 remote_wsgi = remote_wsgi
225 remote_wsgi = remote_wsgi
204 _use_echo_app = False
226 _use_echo_app = False
205
227
206 def __init__(self, settings=None, global_config=None):
228 def __init__(self, settings=None, global_config=None):
207 self._sanitize_settings_and_apply_defaults(settings)
229 self._sanitize_settings_and_apply_defaults(settings)
208
230
209 self.config = Configurator(settings=settings)
231 self.config = Configurator(settings=settings)
210 self.global_config = global_config
232 self.global_config = global_config
211 self.config.include('vcsserver.lib.rc_cache')
233 self.config.include('vcsserver.lib.rc_cache')
212
234
213 locale = settings.get('locale', '') or 'en_US.UTF-8'
235 locale = settings.get('locale', '') or 'en_US.UTF-8'
214 vcs = VCS(locale=locale, cache_config=settings)
236 vcs = VCS(locale=locale, cache_config=settings)
215 self._remotes = {
237 self._remotes = {
216 'hg': vcs._hg_remote,
238 'hg': vcs._hg_remote,
217 'git': vcs._git_remote,
239 'git': vcs._git_remote,
218 'svn': vcs._svn_remote,
240 'svn': vcs._svn_remote,
219 'server': vcs._vcsserver,
241 'server': vcs._vcsserver,
220 }
242 }
221 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
243 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
222 self._use_echo_app = True
244 self._use_echo_app = True
223 log.warning("Using EchoApp for VCS operations.")
245 log.warning("Using EchoApp for VCS operations.")
224 self.remote_wsgi = remote_wsgi_stub
246 self.remote_wsgi = remote_wsgi_stub
225
247
226 self._configure_settings(global_config, settings)
248 self._configure_settings(global_config, settings)
227 self._configure()
249 self._configure()
228
250
229 def _configure_settings(self, global_config, app_settings):
251 def _configure_settings(self, global_config, app_settings):
230 """
252 """
231 Configure the settings module.
253 Configure the settings module.
232 """
254 """
233 settings_merged = global_config.copy()
255 settings_merged = global_config.copy()
234 settings_merged.update(app_settings)
256 settings_merged.update(app_settings)
235
257
236 git_path = app_settings.get('git_path', None)
258 git_path = app_settings.get('git_path', None)
237 if git_path:
259 if git_path:
238 settings.GIT_EXECUTABLE = git_path
260 settings.GIT_EXECUTABLE = git_path
239 binary_dir = app_settings.get('core.binary_dir', None)
261 binary_dir = app_settings.get('core.binary_dir', None)
240 if binary_dir:
262 if binary_dir:
241 settings.BINARY_DIR = binary_dir
263 settings.BINARY_DIR = binary_dir
242
264
243 # Store the settings to make them available to other modules.
265 # Store the settings to make them available to other modules.
244 vcsserver.PYRAMID_SETTINGS = settings_merged
266 vcsserver.PYRAMID_SETTINGS = settings_merged
245 vcsserver.CONFIG = settings_merged
267 vcsserver.CONFIG = settings_merged
246
268
247 def _sanitize_settings_and_apply_defaults(self, settings):
269 def _sanitize_settings_and_apply_defaults(self, settings):
248 temp_store = tempfile.gettempdir()
270 temp_store = tempfile.gettempdir()
249 default_cache_dir = os.path.join(temp_store, 'rc_cache')
271 default_cache_dir = os.path.join(temp_store, 'rc_cache')
250
272
251 # save default, cache dir, and use it for all backends later.
273 # save default, cache dir, and use it for all backends later.
252 default_cache_dir = _string_setting(
274 default_cache_dir = _string_setting(
253 settings,
275 settings,
254 'cache_dir',
276 'cache_dir',
255 default_cache_dir, lower=False, default_when_empty=True)
277 default_cache_dir, lower=False, default_when_empty=True)
256
278
257 # ensure we have our dir created
279 # ensure we have our dir created
258 if not os.path.isdir(default_cache_dir):
280 if not os.path.isdir(default_cache_dir):
259 os.makedirs(default_cache_dir, mode=0755)
281 os.makedirs(default_cache_dir, mode=0755)
260
282
261 # exception store cache
283 # exception store cache
262 _string_setting(
284 _string_setting(
263 settings,
285 settings,
264 'exception_tracker.store_path',
286 'exception_tracker.store_path',
265 temp_store, lower=False, default_when_empty=True)
287 temp_store, lower=False, default_when_empty=True)
266
288
267 # repo_object cache
289 # repo_object cache
268 _string_setting(
290 _string_setting(
269 settings,
291 settings,
270 'rc_cache.repo_object.backend',
292 'rc_cache.repo_object.backend',
271 'dogpile.cache.rc.memory_lru')
293 'dogpile.cache.rc.memory_lru')
272 _int_setting(
294 _int_setting(
273 settings,
295 settings,
274 'rc_cache.repo_object.expiration_time',
296 'rc_cache.repo_object.expiration_time',
275 300)
297 300)
276 _int_setting(
298 _int_setting(
277 settings,
299 settings,
278 'rc_cache.repo_object.max_size',
300 'rc_cache.repo_object.max_size',
279 1024)
301 1024)
280
302
281 def _configure(self):
303 def _configure(self):
282 self.config.add_renderer(
304 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
283 name='msgpack',
284 factory=self._msgpack_renderer_factory)
285
305
286 self.config.add_route('service', '/_service')
306 self.config.add_route('service', '/_service')
287 self.config.add_route('status', '/status')
307 self.config.add_route('status', '/status')
288 self.config.add_route('hg_proxy', '/proxy/hg')
308 self.config.add_route('hg_proxy', '/proxy/hg')
289 self.config.add_route('git_proxy', '/proxy/git')
309 self.config.add_route('git_proxy', '/proxy/git')
290 self.config.add_route('vcs', '/{backend}')
310 self.config.add_route('vcs', '/{backend}')
291 self.config.add_route('stream_git', '/stream/git/*repo_name')
311 self.config.add_route('stream_git', '/stream/git/*repo_name')
292 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
312 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
293
313
294 self.config.add_view(
314 self.config.add_view(self.status_view, route_name='status', renderer='json')
295 self.status_view, route_name='status', renderer='json')
315 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
296 self.config.add_view(
297 self.service_view, route_name='service', renderer='msgpack')
298
316
299 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
317 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
300 self.config.add_view(self.git_proxy(), route_name='git_proxy')
318 self.config.add_view(self.git_proxy(), route_name='git_proxy')
301 self.config.add_view(
319 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
302 self.vcs_view, route_name='vcs', renderer='msgpack',
320 vcs_view=self._remotes)
303 custom_predicates=[self.is_vcs_view])
304
321
305 self.config.add_view(self.hg_stream(), route_name='stream_hg')
322 self.config.add_view(self.hg_stream(), route_name='stream_hg')
306 self.config.add_view(self.git_stream(), route_name='stream_git')
323 self.config.add_view(self.git_stream(), route_name='stream_git')
307
324
308 def notfound(request):
325 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
309 return {'status': '404 NOT FOUND'}
326
310 self.config.add_notfound_view(notfound, renderer='json')
327 self.config.add_notfound_view(not_found, renderer='json')
311
328
312 self.config.add_view(self.handle_vcs_exception, context=Exception)
329 self.config.add_view(self.handle_vcs_exception, context=Exception)
313
330
314 self.config.add_tween(
331 self.config.add_tween(
315 'vcsserver.tweens.RequestWrapperTween',
332 'vcsserver.tweens.RequestWrapperTween',
316 )
333 )
317
334
318 def wsgi_app(self):
335 def wsgi_app(self):
319 return self.config.make_wsgi_app()
336 return self.config.make_wsgi_app()
320
337
321 def vcs_view(self, request):
338 def vcs_view(self, request):
322 remote = self._remotes[request.matchdict['backend']]
339 remote = self._remotes[request.matchdict['backend']]
323 payload = msgpack.unpackb(request.body, use_list=True)
340 payload = msgpack.unpackb(request.body, use_list=True)
324 method = payload.get('method')
341 method = payload.get('method')
325 params = payload.get('params')
342 params = payload.get('params')
326 wire = params.get('wire')
343 wire = params.get('wire')
327 args = params.get('args')
344 args = params.get('args')
328 kwargs = params.get('kwargs')
345 kwargs = params.get('kwargs')
329 context_uid = None
346 context_uid = None
330
347
331 if wire:
348 if wire:
332 try:
349 try:
333 wire['context'] = context_uid = uuid.UUID(wire['context'])
350 wire['context'] = context_uid = uuid.UUID(wire['context'])
334 except KeyError:
351 except KeyError:
335 pass
352 pass
336 args.insert(0, wire)
353 args.insert(0, wire)
337
354
338 log.debug('method called:%s with kwargs:%s context_uid: %s',
355 log.debug('method called:%s with kwargs:%s context_uid: %s',
339 method, kwargs, context_uid)
356 method, kwargs, context_uid)
340 try:
357 try:
341 resp = getattr(remote, method)(*args, **kwargs)
358 resp = getattr(remote, method)(*args, **kwargs)
342 except Exception as e:
359 except Exception as e:
343 exc_info = list(sys.exc_info())
360 exc_info = list(sys.exc_info())
344 exc_type, exc_value, exc_traceback = exc_info
361 exc_type, exc_value, exc_traceback = exc_info
345
362
346 org_exc = getattr(e, '_org_exc', None)
363 org_exc = getattr(e, '_org_exc', None)
347 org_exc_name = None
364 org_exc_name = None
348 if org_exc:
365 if org_exc:
349 org_exc_name = org_exc.__class__.__name__
366 org_exc_name = org_exc.__class__.__name__
350 # replace our "faked" exception with our org
367 # replace our "faked" exception with our org
351 exc_info[0] = org_exc.__class__
368 exc_info[0] = org_exc.__class__
352 exc_info[1] = org_exc
369 exc_info[1] = org_exc
353
370
354 store_exception(id(exc_info), exc_info)
371 store_exception(id(exc_info), exc_info)
355
372
356 tb_info = ''.join(
373 tb_info = ''.join(
357 traceback.format_exception(exc_type, exc_value, exc_traceback))
374 traceback.format_exception(exc_type, exc_value, exc_traceback))
358
375
359 type_ = e.__class__.__name__
376 type_ = e.__class__.__name__
360 if type_ not in self.ALLOWED_EXCEPTIONS:
377 if type_ not in self.ALLOWED_EXCEPTIONS:
361 type_ = None
378 type_ = None
362
379
363 resp = {
380 resp = {
364 'id': payload.get('id'),
381 'id': payload.get('id'),
365 'error': {
382 'error': {
366 'message': e.message,
383 'message': e.message,
367 'traceback': tb_info,
384 'traceback': tb_info,
368 'org_exc': org_exc_name,
385 'org_exc': org_exc_name,
369 'type': type_
386 'type': type_
370 }
387 }
371 }
388 }
372 try:
389 try:
373 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
390 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
374 except AttributeError:
391 except AttributeError:
375 pass
392 pass
376 else:
393 else:
377 resp = {
394 resp = {
378 'id': payload.get('id'),
395 'id': payload.get('id'),
379 'result': resp
396 'result': resp
380 }
397 }
381
398
382 return resp
399 return resp
383
400
384 def status_view(self, request):
401 def status_view(self, request):
385 import vcsserver
402 import vcsserver
386 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
403 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
387 'pid': os.getpid()}
404 'pid': os.getpid()}
388
405
389 def service_view(self, request):
406 def service_view(self, request):
390 import vcsserver
407 import vcsserver
391
408
392 payload = msgpack.unpackb(request.body, use_list=True)
409 payload = msgpack.unpackb(request.body, use_list=True)
393
410
394 try:
411 try:
395 path = self.global_config['__file__']
412 path = self.global_config['__file__']
396 config = configparser.ConfigParser()
413 config = configparser.ConfigParser()
397 config.read(path)
414 config.read(path)
398 parsed_ini = config
415 parsed_ini = config
399 if parsed_ini.has_section('server:main'):
416 if parsed_ini.has_section('server:main'):
400 parsed_ini = dict(parsed_ini.items('server:main'))
417 parsed_ini = dict(parsed_ini.items('server:main'))
401 except Exception:
418 except Exception:
402 log.exception('Failed to read .ini file for display')
419 log.exception('Failed to read .ini file for display')
403 parsed_ini = {}
420 parsed_ini = {}
404
421
405 resp = {
422 resp = {
406 'id': payload.get('id'),
423 'id': payload.get('id'),
407 'result': dict(
424 'result': dict(
408 version=vcsserver.__version__,
425 version=vcsserver.__version__,
409 config=parsed_ini,
426 config=parsed_ini,
410 payload=payload,
427 payload=payload,
411 )
428 )
412 }
429 }
413 return resp
430 return resp
414
431
415 def _msgpack_renderer_factory(self, info):
432 def _msgpack_renderer_factory(self, info):
416 def _render(value, system):
433 def _render(value, system):
417 value = msgpack.packb(value)
434 value = msgpack.packb(value)
418 request = system.get('request')
435 request = system.get('request')
419 if request is not None:
436 if request is not None:
420 response = request.response
437 response = request.response
421 ct = response.content_type
438 ct = response.content_type
422 if ct == response.default_content_type:
439 if ct == response.default_content_type:
423 response.content_type = 'application/x-msgpack'
440 response.content_type = 'application/x-msgpack'
424 return value
441 return value
425 return _render
442 return _render
426
443
427 def set_env_from_config(self, environ, config):
444 def set_env_from_config(self, environ, config):
428 dict_conf = {}
445 dict_conf = {}
429 try:
446 try:
430 for elem in config:
447 for elem in config:
431 if elem[0] == 'rhodecode':
448 if elem[0] == 'rhodecode':
432 dict_conf = json.loads(elem[2])
449 dict_conf = json.loads(elem[2])
433 break
450 break
434 except Exception:
451 except Exception:
435 log.exception('Failed to fetch SCM CONFIG')
452 log.exception('Failed to fetch SCM CONFIG')
436 return
453 return
437
454
438 username = dict_conf.get('username')
455 username = dict_conf.get('username')
439 if username:
456 if username:
440 environ['REMOTE_USER'] = username
457 environ['REMOTE_USER'] = username
441 # mercurial specific, some extension api rely on this
458 # mercurial specific, some extension api rely on this
442 environ['HGUSER'] = username
459 environ['HGUSER'] = username
443
460
444 ip = dict_conf.get('ip')
461 ip = dict_conf.get('ip')
445 if ip:
462 if ip:
446 environ['REMOTE_HOST'] = ip
463 environ['REMOTE_HOST'] = ip
447
464
448 if _is_request_chunked(environ):
465 if _is_request_chunked(environ):
449 # set the compatibility flag for webob
466 # set the compatibility flag for webob
450 environ['wsgi.input_terminated'] = True
467 environ['wsgi.input_terminated'] = True
451
468
452 def hg_proxy(self):
469 def hg_proxy(self):
453 @wsgiapp
470 @wsgiapp
454 def _hg_proxy(environ, start_response):
471 def _hg_proxy(environ, start_response):
455 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
472 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
456 return app(environ, start_response)
473 return app(environ, start_response)
457 return _hg_proxy
474 return _hg_proxy
458
475
459 def git_proxy(self):
476 def git_proxy(self):
460 @wsgiapp
477 @wsgiapp
461 def _git_proxy(environ, start_response):
478 def _git_proxy(environ, start_response):
462 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
479 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
463 return app(environ, start_response)
480 return app(environ, start_response)
464 return _git_proxy
481 return _git_proxy
465
482
466 def hg_stream(self):
483 def hg_stream(self):
467 if self._use_echo_app:
484 if self._use_echo_app:
468 @wsgiapp
485 @wsgiapp
469 def _hg_stream(environ, start_response):
486 def _hg_stream(environ, start_response):
470 app = EchoApp('fake_path', 'fake_name', None)
487 app = EchoApp('fake_path', 'fake_name', None)
471 return app(environ, start_response)
488 return app(environ, start_response)
472 return _hg_stream
489 return _hg_stream
473 else:
490 else:
474 @wsgiapp
491 @wsgiapp
475 def _hg_stream(environ, start_response):
492 def _hg_stream(environ, start_response):
476 log.debug('http-app: handling hg stream')
493 log.debug('http-app: handling hg stream')
477 repo_path = environ['HTTP_X_RC_REPO_PATH']
494 repo_path = environ['HTTP_X_RC_REPO_PATH']
478 repo_name = environ['HTTP_X_RC_REPO_NAME']
495 repo_name = environ['HTTP_X_RC_REPO_NAME']
479 packed_config = base64.b64decode(
496 packed_config = base64.b64decode(
480 environ['HTTP_X_RC_REPO_CONFIG'])
497 environ['HTTP_X_RC_REPO_CONFIG'])
481 config = msgpack.unpackb(packed_config)
498 config = msgpack.unpackb(packed_config)
482 app = scm_app.create_hg_wsgi_app(
499 app = scm_app.create_hg_wsgi_app(
483 repo_path, repo_name, config)
500 repo_path, repo_name, config)
484
501
485 # Consistent path information for hgweb
502 # Consistent path information for hgweb
486 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
503 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
487 environ['REPO_NAME'] = repo_name
504 environ['REPO_NAME'] = repo_name
488 self.set_env_from_config(environ, config)
505 self.set_env_from_config(environ, config)
489
506
490 log.debug('http-app: starting app handler '
507 log.debug('http-app: starting app handler '
491 'with %s and process request', app)
508 'with %s and process request', app)
492 return app(environ, ResponseFilter(start_response))
509 return app(environ, ResponseFilter(start_response))
493 return _hg_stream
510 return _hg_stream
494
511
495 def git_stream(self):
512 def git_stream(self):
496 if self._use_echo_app:
513 if self._use_echo_app:
497 @wsgiapp
514 @wsgiapp
498 def _git_stream(environ, start_response):
515 def _git_stream(environ, start_response):
499 app = EchoApp('fake_path', 'fake_name', None)
516 app = EchoApp('fake_path', 'fake_name', None)
500 return app(environ, start_response)
517 return app(environ, start_response)
501 return _git_stream
518 return _git_stream
502 else:
519 else:
503 @wsgiapp
520 @wsgiapp
504 def _git_stream(environ, start_response):
521 def _git_stream(environ, start_response):
505 log.debug('http-app: handling git stream')
522 log.debug('http-app: handling git stream')
506 repo_path = environ['HTTP_X_RC_REPO_PATH']
523 repo_path = environ['HTTP_X_RC_REPO_PATH']
507 repo_name = environ['HTTP_X_RC_REPO_NAME']
524 repo_name = environ['HTTP_X_RC_REPO_NAME']
508 packed_config = base64.b64decode(
525 packed_config = base64.b64decode(
509 environ['HTTP_X_RC_REPO_CONFIG'])
526 environ['HTTP_X_RC_REPO_CONFIG'])
510 config = msgpack.unpackb(packed_config)
527 config = msgpack.unpackb(packed_config)
511
528
512 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
529 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
513 self.set_env_from_config(environ, config)
530 self.set_env_from_config(environ, config)
514
531
515 content_type = environ.get('CONTENT_TYPE', '')
532 content_type = environ.get('CONTENT_TYPE', '')
516
533
517 path = environ['PATH_INFO']
534 path = environ['PATH_INFO']
518 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
535 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
519 log.debug(
536 log.debug(
520 'LFS: Detecting if request `%s` is LFS server path based '
537 'LFS: Detecting if request `%s` is LFS server path based '
521 'on content type:`%s`, is_lfs:%s',
538 'on content type:`%s`, is_lfs:%s',
522 path, content_type, is_lfs_request)
539 path, content_type, is_lfs_request)
523
540
524 if not is_lfs_request:
541 if not is_lfs_request:
525 # fallback detection by path
542 # fallback detection by path
526 if GIT_LFS_PROTO_PAT.match(path):
543 if GIT_LFS_PROTO_PAT.match(path):
527 is_lfs_request = True
544 is_lfs_request = True
528 log.debug(
545 log.debug(
529 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
546 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
530 path, is_lfs_request)
547 path, is_lfs_request)
531
548
532 if is_lfs_request:
549 if is_lfs_request:
533 app = scm_app.create_git_lfs_wsgi_app(
550 app = scm_app.create_git_lfs_wsgi_app(
534 repo_path, repo_name, config)
551 repo_path, repo_name, config)
535 else:
552 else:
536 app = scm_app.create_git_wsgi_app(
553 app = scm_app.create_git_wsgi_app(
537 repo_path, repo_name, config)
554 repo_path, repo_name, config)
538
555
539 log.debug('http-app: starting app handler '
556 log.debug('http-app: starting app handler '
540 'with %s and process request', app)
557 'with %s and process request', app)
541
558
542 return app(environ, start_response)
559 return app(environ, start_response)
543
560
544 return _git_stream
561 return _git_stream
545
562
546 def is_vcs_view(self, context, request):
547 """
548 View predicate that returns true if given backend is supported by
549 defined remotes.
550 """
551 backend = request.matchdict.get('backend')
552 return backend in self._remotes
553
554 def handle_vcs_exception(self, exception, request):
563 def handle_vcs_exception(self, exception, request):
555 _vcs_kind = getattr(exception, '_vcs_kind', '')
564 _vcs_kind = getattr(exception, '_vcs_kind', '')
556 if _vcs_kind == 'repo_locked':
565 if _vcs_kind == 'repo_locked':
557 # Get custom repo-locked status code if present.
566 # Get custom repo-locked status code if present.
558 status_code = request.headers.get('X-RC-Locked-Status-Code')
567 status_code = request.headers.get('X-RC-Locked-Status-Code')
559 return HTTPRepoLocked(
568 return HTTPRepoLocked(
560 title=exception.message, status_code=status_code)
569 title=exception.message, status_code=status_code)
561
570
562 elif _vcs_kind == 'repo_branch_protected':
571 elif _vcs_kind == 'repo_branch_protected':
563 # Get custom repo-branch-protected status code if present.
572 # Get custom repo-branch-protected status code if present.
564 return HTTPRepoBranchProtected(title=exception.message)
573 return HTTPRepoBranchProtected(title=exception.message)
565
574
566 exc_info = request.exc_info
575 exc_info = request.exc_info
567 store_exception(id(exc_info), exc_info)
576 store_exception(id(exc_info), exc_info)
568
577
569 traceback_info = 'unavailable'
578 traceback_info = 'unavailable'
570 if request.exc_info:
579 if request.exc_info:
571 exc_type, exc_value, exc_tb = request.exc_info
580 exc_type, exc_value, exc_tb = request.exc_info
572 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
581 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
573
582
574 log.error(
583 log.error(
575 'error occurred handling this request for path: %s, \n tb: %s',
584 'error occurred handling this request for path: %s, \n tb: %s',
576 request.path, traceback_info)
585 request.path, traceback_info)
577 raise exception
586 raise exception
578
587
579
588
580 class ResponseFilter(object):
589 class ResponseFilter(object):
581
590
582 def __init__(self, start_response):
591 def __init__(self, start_response):
583 self._start_response = start_response
592 self._start_response = start_response
584
593
585 def __call__(self, status, response_headers, exc_info=None):
594 def __call__(self, status, response_headers, exc_info=None):
586 headers = tuple(
595 headers = tuple(
587 (h, v) for h, v in response_headers
596 (h, v) for h, v in response_headers
588 if not wsgiref.util.is_hop_by_hop(h))
597 if not wsgiref.util.is_hop_by_hop(h))
589 return self._start_response(status, headers, exc_info)
598 return self._start_response(status, headers, exc_info)
590
599
591
600
592 def main(global_config, **settings):
601 def main(global_config, **settings):
593 if MercurialFactory:
602 if MercurialFactory:
594 hgpatches.patch_largefiles_capabilities()
603 hgpatches.patch_largefiles_capabilities()
595 hgpatches.patch_subrepo_type_mapping()
604 hgpatches.patch_subrepo_type_mapping()
596
605
597 app = HTTPApplication(settings=settings, global_config=global_config)
606 app = HTTPApplication(settings=settings, global_config=global_config)
598 return app.wsgi_app()
607 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now