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