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