##// END OF EJS Templates
debugging: expose logs/exception when debug log is enabled.
super-admin -
r4768:f604047c default
parent child Browse files
Show More
@@ -1,782 +1,785 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import sys
23 23 import logging
24 24 import collections
25 25 import tempfile
26 26 import time
27 27
28 28 from paste.gzipper import make_gzip_middleware
29 29 import pyramid.events
30 30 from pyramid.wsgi import wsgiapp
31 31 from pyramid.authorization import ACLAuthorizationPolicy
32 32 from pyramid.config import Configurator
33 33 from pyramid.settings import asbool, aslist
34 34 from pyramid.httpexceptions import (
35 35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 36 from pyramid.renderers import render_to_response
37 37
38 38 from rhodecode.model import meta
39 39 from rhodecode.config import patches
40 40 from rhodecode.config import utils as config_utils
41 41 from rhodecode.config.environment import load_pyramid_environment
42 42
43 43 import rhodecode.events
44 44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 45 from rhodecode.lib.request import Request
46 46 from rhodecode.lib.vcs import VCSCommunicationError
47 47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 50 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 51 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
52 52 from rhodecode.lib.exc_tracking import store_exception
53 53 from rhodecode.subscribers import (
54 54 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 55 write_metadata_if_needed, write_usage_data)
56 56
57 57
58 58 log = logging.getLogger(__name__)
59 59
60 60
61 61 def is_http_error(response):
62 62 # error which should have traceback
63 63 return response.status_code > 499
64 64
65 65
66 66 def should_load_all():
67 67 """
68 68 Returns if all application components should be loaded. In some cases it's
69 69 desired to skip apps loading for faster shell script execution
70 70 """
71 71 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
72 72 if ssh_cmd:
73 73 return False
74 74
75 75 return True
76 76
77 77
78 78 def make_pyramid_app(global_config, **settings):
79 79 """
80 80 Constructs the WSGI application based on Pyramid.
81 81
82 82 Specials:
83 83
84 84 * The application can also be integrated like a plugin via the call to
85 85 `includeme`. This is accompanied with the other utility functions which
86 86 are called. Changing this should be done with great care to not break
87 87 cases when these fragments are assembled from another place.
88 88
89 89 """
90 90
91 91 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
92 92 # will be replaced by the value of the environment variable "NAME" in this case.
93 93 start_time = time.time()
94 94 log.info('Pyramid app config starting')
95 95
96 96 debug = asbool(global_config.get('debug'))
97 97 if debug:
98 98 enable_debug()
99 99
100 100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
101 101
102 102 global_config = _substitute_values(global_config, environ)
103 103 settings = _substitute_values(settings, environ)
104 104
105 105 sanitize_settings_and_apply_defaults(global_config, settings)
106 106
107 107 config = Configurator(settings=settings)
108 108
109 109 # Apply compatibility patches
110 110 patches.inspect_getargspec()
111 111
112 112 load_pyramid_environment(global_config, settings)
113 113
114 114 # Static file view comes first
115 115 includeme_first(config)
116 116
117 117 includeme(config)
118 118
119 119 pyramid_app = config.make_wsgi_app()
120 120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
121 121 pyramid_app.config = config
122 122
123 123 config.configure_celery(global_config['__file__'])
124 124
125 125 # creating the app uses a connection - return it after we are done
126 126 meta.Session.remove()
127 127 total_time = time.time() - start_time
128 128 log.info('Pyramid app `%s` created and configured in %.2fs',
129 129 pyramid_app.func_name, total_time)
130 130
131 131 return pyramid_app
132 132
133 133
134 134 def not_found_view(request):
135 135 """
136 136 This creates the view which should be registered as not-found-view to
137 137 pyramid.
138 138 """
139 139
140 140 if not getattr(request, 'vcs_call', None):
141 141 # handle like regular case with our error_handler
142 142 return error_handler(HTTPNotFound(), request)
143 143
144 144 # handle not found view as a vcs call
145 145 settings = request.registry.settings
146 146 ae_client = getattr(request, 'ae_client', None)
147 147 vcs_app = VCSMiddleware(
148 148 HTTPNotFound(), request.registry, settings,
149 149 appenlight_client=ae_client)
150 150
151 151 return wsgiapp(vcs_app)(None, request)
152 152
153 153
154 154 def error_handler(exception, request):
155 155 import rhodecode
156 156 from rhodecode.lib import helpers
157 from rhodecode.lib.utils2 import str2bool
157 158
158 159 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
159 160
160 161 base_response = HTTPInternalServerError()
161 162 # prefer original exception for the response since it may have headers set
162 163 if isinstance(exception, HTTPException):
163 164 base_response = exception
164 165 elif isinstance(exception, VCSCommunicationError):
165 166 base_response = VCSServerUnavailable()
166 167
167 168 if is_http_error(base_response):
168 169 log.exception(
169 170 'error occurred handling this request for path: %s', request.path)
170 171
171 172 error_explanation = base_response.explanation or str(base_response)
172 173 if base_response.status_code == 404:
173 174 error_explanation += " Optionally you don't have permission to access this page."
174 175 c = AttributeDict()
175 176 c.error_message = base_response.status
176 177 c.error_explanation = error_explanation
177 178 c.visual = AttributeDict()
178 179
179 180 c.visual.rhodecode_support_url = (
180 181 request.registry.settings.get('rhodecode_support_url') or
181 182 request.route_url('rhodecode_support')
182 183 )
183 184 c.redirect_time = 0
184 185 c.rhodecode_name = rhodecode_title
185 186 if not c.rhodecode_name:
186 187 c.rhodecode_name = 'Rhodecode'
187 188
188 189 c.causes = []
189 190 if is_http_error(base_response):
190 191 c.causes.append('Server is overloaded.')
191 192 c.causes.append('Server database connection is lost.')
192 193 c.causes.append('Server expected unhandled error.')
193 194
194 195 if hasattr(base_response, 'causes'):
195 196 c.causes = base_response.causes
196 197
197 198 c.messages = helpers.flash.pop_messages(request=request)
198 199
199 200 exc_info = sys.exc_info()
200 201 c.exception_id = id(exc_info)
201 202 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
202 203 or base_response.status_code > 499
203 204 c.exception_id_url = request.route_url(
204 205 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
205 206
206 207 if c.show_exception_id:
207 208 store_exception(c.exception_id, exc_info)
209 c.exception_debug = str2bool(rhodecode.CONFIG.get('debug'))
210 c.exception_config_ini = rhodecode.CONFIG.get('__file__')
208 211
209 212 response = render_to_response(
210 213 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
211 214 response=base_response)
212 215
213 216 return response
214 217
215 218
216 219 def includeme_first(config):
217 220 # redirect automatic browser favicon.ico requests to correct place
218 221 def favicon_redirect(context, request):
219 222 return HTTPFound(
220 223 request.static_path('rhodecode:public/images/favicon.ico'))
221 224
222 225 config.add_view(favicon_redirect, route_name='favicon')
223 226 config.add_route('favicon', '/favicon.ico')
224 227
225 228 def robots_redirect(context, request):
226 229 return HTTPFound(
227 230 request.static_path('rhodecode:public/robots.txt'))
228 231
229 232 config.add_view(robots_redirect, route_name='robots')
230 233 config.add_route('robots', '/robots.txt')
231 234
232 235 config.add_static_view(
233 236 '_static/deform', 'deform:static')
234 237 config.add_static_view(
235 238 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
236 239
237 240
238 241 def includeme(config, auth_resources=None):
239 242 from rhodecode.lib.celerylib.loader import configure_celery
240 243 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
241 244 settings = config.registry.settings
242 245 config.set_request_factory(Request)
243 246
244 247 # plugin information
245 248 config.registry.rhodecode_plugins = collections.OrderedDict()
246 249
247 250 config.add_directive(
248 251 'register_rhodecode_plugin', register_rhodecode_plugin)
249 252
250 253 config.add_directive('configure_celery', configure_celery)
251 254
252 255 if asbool(settings.get('appenlight', 'false')):
253 256 config.include('appenlight_client.ext.pyramid_tween')
254 257
255 258 load_all = should_load_all()
256 259
257 260 # Includes which are required. The application would fail without them.
258 261 config.include('pyramid_mako')
259 262 config.include('rhodecode.lib.rc_beaker')
260 263 config.include('rhodecode.lib.rc_cache')
261 264 config.include('rhodecode.apps._base.navigation')
262 265 config.include('rhodecode.apps._base.subscribers')
263 266 config.include('rhodecode.tweens')
264 267 config.include('rhodecode.authentication')
265 268
266 269 if load_all:
267 270 ce_auth_resources = [
268 271 'rhodecode.authentication.plugins.auth_crowd',
269 272 'rhodecode.authentication.plugins.auth_headers',
270 273 'rhodecode.authentication.plugins.auth_jasig_cas',
271 274 'rhodecode.authentication.plugins.auth_ldap',
272 275 'rhodecode.authentication.plugins.auth_pam',
273 276 'rhodecode.authentication.plugins.auth_rhodecode',
274 277 'rhodecode.authentication.plugins.auth_token',
275 278 ]
276 279
277 280 # load CE authentication plugins
278 281
279 282 if auth_resources:
280 283 ce_auth_resources.extend(auth_resources)
281 284
282 285 for resource in ce_auth_resources:
283 286 config.include(resource)
284 287
285 288 # Auto discover authentication plugins and include their configuration.
286 289 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
287 290 from rhodecode.authentication import discover_legacy_plugins
288 291 discover_legacy_plugins(config)
289 292
290 293 # apps
291 294 if load_all:
292 295 config.include('rhodecode.api')
293 296 config.include('rhodecode.apps._base')
294 297 config.include('rhodecode.apps.hovercards')
295 298 config.include('rhodecode.apps.ops')
296 299 config.include('rhodecode.apps.channelstream')
297 300 config.include('rhodecode.apps.file_store')
298 301 config.include('rhodecode.apps.admin')
299 302 config.include('rhodecode.apps.login')
300 303 config.include('rhodecode.apps.home')
301 304 config.include('rhodecode.apps.journal')
302 305
303 306 config.include('rhodecode.apps.repository')
304 307 config.include('rhodecode.apps.repo_group')
305 308 config.include('rhodecode.apps.user_group')
306 309 config.include('rhodecode.apps.search')
307 310 config.include('rhodecode.apps.user_profile')
308 311 config.include('rhodecode.apps.user_group_profile')
309 312 config.include('rhodecode.apps.my_account')
310 313 config.include('rhodecode.apps.gist')
311 314
312 315 config.include('rhodecode.apps.svn_support')
313 316 config.include('rhodecode.apps.ssh_support')
314 317 config.include('rhodecode.apps.debug_style')
315 318
316 319 if load_all:
317 320 config.include('rhodecode.integrations')
318 321
319 322 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
320 323 config.add_translation_dirs('rhodecode:i18n/')
321 324 settings['default_locale_name'] = settings.get('lang', 'en')
322 325
323 326 # Add subscribers.
324 327 if load_all:
325 328 config.add_subscriber(scan_repositories_if_enabled,
326 329 pyramid.events.ApplicationCreated)
327 330 config.add_subscriber(write_metadata_if_needed,
328 331 pyramid.events.ApplicationCreated)
329 332 config.add_subscriber(write_usage_data,
330 333 pyramid.events.ApplicationCreated)
331 334 config.add_subscriber(write_js_routes_if_enabled,
332 335 pyramid.events.ApplicationCreated)
333 336
334 337 # request custom methods
335 338 config.add_request_method(
336 339 'rhodecode.lib.partial_renderer.get_partial_renderer',
337 340 'get_partial_renderer')
338 341
339 342 config.add_request_method(
340 343 'rhodecode.lib.request_counter.get_request_counter',
341 344 'request_count')
342 345
343 346 config.add_request_method(
344 347 'rhodecode.lib._vendor.statsd.get_statsd_client',
345 348 'statsd', reify=True)
346 349
347 350 # Set the authorization policy.
348 351 authz_policy = ACLAuthorizationPolicy()
349 352 config.set_authorization_policy(authz_policy)
350 353
351 354 # Set the default renderer for HTML templates to mako.
352 355 config.add_mako_renderer('.html')
353 356
354 357 config.add_renderer(
355 358 name='json_ext',
356 359 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
357 360
358 361 config.add_renderer(
359 362 name='string_html',
360 363 factory='rhodecode.lib.string_renderer.html')
361 364
362 365 # include RhodeCode plugins
363 366 includes = aslist(settings.get('rhodecode.includes', []))
364 367 for inc in includes:
365 368 config.include(inc)
366 369
367 370 # custom not found view, if our pyramid app doesn't know how to handle
368 371 # the request pass it to potential VCS handling ap
369 372 config.add_notfound_view(not_found_view)
370 373 if not settings.get('debugtoolbar.enabled', False):
371 374 # disabled debugtoolbar handle all exceptions via the error_handlers
372 375 config.add_view(error_handler, context=Exception)
373 376
374 377 # all errors including 403/404/50X
375 378 config.add_view(error_handler, context=HTTPError)
376 379
377 380
378 381 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
379 382 """
380 383 Apply outer WSGI middlewares around the application.
381 384 """
382 385 registry = config.registry
383 386 settings = registry.settings
384 387
385 388 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
386 389 pyramid_app = HttpsFixup(pyramid_app, settings)
387 390
388 391 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
389 392 pyramid_app, settings)
390 393 registry.ae_client = _ae_client
391 394
392 395 if settings['gzip_responses']:
393 396 pyramid_app = make_gzip_middleware(
394 397 pyramid_app, settings, compress_level=1)
395 398
396 399 # this should be the outer most middleware in the wsgi stack since
397 400 # middleware like Routes make database calls
398 401 def pyramid_app_with_cleanup(environ, start_response):
399 402 try:
400 403 return pyramid_app(environ, start_response)
401 404 finally:
402 405 # Dispose current database session and rollback uncommitted
403 406 # transactions.
404 407 meta.Session.remove()
405 408
406 409 # In a single threaded mode server, on non sqlite db we should have
407 410 # '0 Current Checked out connections' at the end of a request,
408 411 # if not, then something, somewhere is leaving a connection open
409 412 pool = meta.Base.metadata.bind.engine.pool
410 413 log.debug('sa pool status: %s', pool.status())
411 414 log.debug('Request processing finalized')
412 415
413 416 return pyramid_app_with_cleanup
414 417
415 418
416 419 def sanitize_settings_and_apply_defaults(global_config, settings):
417 420 """
418 421 Applies settings defaults and does all type conversion.
419 422
420 423 We would move all settings parsing and preparation into this place, so that
421 424 we have only one place left which deals with this part. The remaining parts
422 425 of the application would start to rely fully on well prepared settings.
423 426
424 427 This piece would later be split up per topic to avoid a big fat monster
425 428 function.
426 429 """
427 430
428 431 settings.setdefault('rhodecode.edition', 'Community Edition')
429 432 settings.setdefault('rhodecode.edition_id', 'CE')
430 433
431 434 if 'mako.default_filters' not in settings:
432 435 # set custom default filters if we don't have it defined
433 436 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
434 437 settings['mako.default_filters'] = 'h_filter'
435 438
436 439 if 'mako.directories' not in settings:
437 440 mako_directories = settings.setdefault('mako.directories', [
438 441 # Base templates of the original application
439 442 'rhodecode:templates',
440 443 ])
441 444 log.debug(
442 445 "Using the following Mako template directories: %s",
443 446 mako_directories)
444 447
445 448 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
446 449 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
447 450 raw_url = settings['beaker.session.url']
448 451 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
449 452 settings['beaker.session.url'] = 'redis://' + raw_url
450 453
451 454 # Default includes, possible to change as a user
452 455 pyramid_includes = settings.setdefault('pyramid.includes', [])
453 456 log.debug(
454 457 "Using the following pyramid.includes: %s",
455 458 pyramid_includes)
456 459
457 460 # TODO: johbo: Re-think this, usually the call to config.include
458 461 # should allow to pass in a prefix.
459 462 settings.setdefault('rhodecode.api.url', '/_admin/api')
460 463 settings.setdefault('__file__', global_config.get('__file__'))
461 464
462 465 # Sanitize generic settings.
463 466 _list_setting(settings, 'default_encoding', 'UTF-8')
464 467 _bool_setting(settings, 'is_test', 'false')
465 468 _bool_setting(settings, 'gzip_responses', 'false')
466 469
467 470 # Call split out functions that sanitize settings for each topic.
468 471 _sanitize_appenlight_settings(settings)
469 472 _sanitize_vcs_settings(settings)
470 473 _sanitize_cache_settings(settings)
471 474
472 475 # configure instance id
473 476 config_utils.set_instance_id(settings)
474 477
475 478 return settings
476 479
477 480
478 481 def enable_debug():
479 482 """
480 483 Helper to enable debug on running instance
481 484 :return:
482 485 """
483 486 import tempfile
484 487 import textwrap
485 488 import logging.config
486 489
487 490 ini_template = textwrap.dedent("""
488 491 #####################################
489 492 ### DEBUG LOGGING CONFIGURATION ####
490 493 #####################################
491 494 [loggers]
492 495 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
493 496
494 497 [handlers]
495 498 keys = console, console_sql
496 499
497 500 [formatters]
498 501 keys = generic, color_formatter, color_formatter_sql
499 502
500 503 #############
501 504 ## LOGGERS ##
502 505 #############
503 506 [logger_root]
504 507 level = NOTSET
505 508 handlers = console
506 509
507 510 [logger_sqlalchemy]
508 511 level = INFO
509 512 handlers = console_sql
510 513 qualname = sqlalchemy.engine
511 514 propagate = 0
512 515
513 516 [logger_beaker]
514 517 level = DEBUG
515 518 handlers =
516 519 qualname = beaker.container
517 520 propagate = 1
518 521
519 522 [logger_rhodecode]
520 523 level = DEBUG
521 524 handlers =
522 525 qualname = rhodecode
523 526 propagate = 1
524 527
525 528 [logger_ssh_wrapper]
526 529 level = DEBUG
527 530 handlers =
528 531 qualname = ssh_wrapper
529 532 propagate = 1
530 533
531 534 [logger_celery]
532 535 level = DEBUG
533 536 handlers =
534 537 qualname = celery
535 538
536 539
537 540 ##############
538 541 ## HANDLERS ##
539 542 ##############
540 543
541 544 [handler_console]
542 545 class = StreamHandler
543 546 args = (sys.stderr, )
544 547 level = DEBUG
545 548 formatter = color_formatter
546 549
547 550 [handler_console_sql]
548 551 # "level = DEBUG" logs SQL queries and results.
549 552 # "level = INFO" logs SQL queries.
550 553 # "level = WARN" logs neither. (Recommended for production systems.)
551 554 class = StreamHandler
552 555 args = (sys.stderr, )
553 556 level = WARN
554 557 formatter = color_formatter_sql
555 558
556 559 ################
557 560 ## FORMATTERS ##
558 561 ################
559 562
560 563 [formatter_generic]
561 564 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
562 565 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
563 566 datefmt = %Y-%m-%d %H:%M:%S
564 567
565 568 [formatter_color_formatter]
566 569 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
567 570 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
568 571 datefmt = %Y-%m-%d %H:%M:%S
569 572
570 573 [formatter_color_formatter_sql]
571 574 class = rhodecode.lib.logging_formatter.ColorFormatterSql
572 575 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
573 576 datefmt = %Y-%m-%d %H:%M:%S
574 577 """)
575 578
576 579 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
577 580 delete=False) as f:
578 581 log.info('Saved Temporary DEBUG config at %s', f.name)
579 582 f.write(ini_template)
580 583
581 584 logging.config.fileConfig(f.name)
582 585 log.debug('DEBUG MODE ON')
583 586 os.remove(f.name)
584 587
585 588
586 589 def _sanitize_appenlight_settings(settings):
587 590 _bool_setting(settings, 'appenlight', 'false')
588 591
589 592
590 593 def _sanitize_vcs_settings(settings):
591 594 """
592 595 Applies settings defaults and does type conversion for all VCS related
593 596 settings.
594 597 """
595 598 _string_setting(settings, 'vcs.svn.compatible_version', '')
596 599 _string_setting(settings, 'vcs.hooks.protocol', 'http')
597 600 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
598 601 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
599 602 _string_setting(settings, 'vcs.server', '')
600 603 _string_setting(settings, 'vcs.server.protocol', 'http')
601 604 _bool_setting(settings, 'startup.import_repos', 'false')
602 605 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
603 606 _bool_setting(settings, 'vcs.server.enable', 'true')
604 607 _bool_setting(settings, 'vcs.start_server', 'false')
605 608 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
606 609 _int_setting(settings, 'vcs.connection_timeout', 3600)
607 610
608 611 # Support legacy values of vcs.scm_app_implementation. Legacy
609 612 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
610 613 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
611 614 scm_app_impl = settings['vcs.scm_app_implementation']
612 615 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
613 616 settings['vcs.scm_app_implementation'] = 'http'
614 617
615 618
616 619 def _sanitize_cache_settings(settings):
617 620 temp_store = tempfile.gettempdir()
618 621 default_cache_dir = os.path.join(temp_store, 'rc_cache')
619 622
620 623 # save default, cache dir, and use it for all backends later.
621 624 default_cache_dir = _string_setting(
622 625 settings,
623 626 'cache_dir',
624 627 default_cache_dir, lower=False, default_when_empty=True)
625 628
626 629 # ensure we have our dir created
627 630 if not os.path.isdir(default_cache_dir):
628 631 os.makedirs(default_cache_dir, mode=0o755)
629 632
630 633 # exception store cache
631 634 _string_setting(
632 635 settings,
633 636 'exception_tracker.store_path',
634 637 temp_store, lower=False, default_when_empty=True)
635 638 _bool_setting(
636 639 settings,
637 640 'exception_tracker.send_email',
638 641 'false')
639 642 _string_setting(
640 643 settings,
641 644 'exception_tracker.email_prefix',
642 645 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
643 646
644 647 # cache_perms
645 648 _string_setting(
646 649 settings,
647 650 'rc_cache.cache_perms.backend',
648 651 'dogpile.cache.rc.file_namespace', lower=False)
649 652 _int_setting(
650 653 settings,
651 654 'rc_cache.cache_perms.expiration_time',
652 655 60)
653 656 _string_setting(
654 657 settings,
655 658 'rc_cache.cache_perms.arguments.filename',
656 659 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
657 660
658 661 # cache_repo
659 662 _string_setting(
660 663 settings,
661 664 'rc_cache.cache_repo.backend',
662 665 'dogpile.cache.rc.file_namespace', lower=False)
663 666 _int_setting(
664 667 settings,
665 668 'rc_cache.cache_repo.expiration_time',
666 669 60)
667 670 _string_setting(
668 671 settings,
669 672 'rc_cache.cache_repo.arguments.filename',
670 673 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
671 674
672 675 # cache_license
673 676 _string_setting(
674 677 settings,
675 678 'rc_cache.cache_license.backend',
676 679 'dogpile.cache.rc.file_namespace', lower=False)
677 680 _int_setting(
678 681 settings,
679 682 'rc_cache.cache_license.expiration_time',
680 683 5*60)
681 684 _string_setting(
682 685 settings,
683 686 'rc_cache.cache_license.arguments.filename',
684 687 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
685 688
686 689 # cache_repo_longterm memory, 96H
687 690 _string_setting(
688 691 settings,
689 692 'rc_cache.cache_repo_longterm.backend',
690 693 'dogpile.cache.rc.memory_lru', lower=False)
691 694 _int_setting(
692 695 settings,
693 696 'rc_cache.cache_repo_longterm.expiration_time',
694 697 345600)
695 698 _int_setting(
696 699 settings,
697 700 'rc_cache.cache_repo_longterm.max_size',
698 701 10000)
699 702
700 703 # sql_cache_short
701 704 _string_setting(
702 705 settings,
703 706 'rc_cache.sql_cache_short.backend',
704 707 'dogpile.cache.rc.memory_lru', lower=False)
705 708 _int_setting(
706 709 settings,
707 710 'rc_cache.sql_cache_short.expiration_time',
708 711 30)
709 712 _int_setting(
710 713 settings,
711 714 'rc_cache.sql_cache_short.max_size',
712 715 10000)
713 716
714 717
715 718 def _int_setting(settings, name, default):
716 719 settings[name] = int(settings.get(name, default))
717 720 return settings[name]
718 721
719 722
720 723 def _bool_setting(settings, name, default):
721 724 input_val = settings.get(name, default)
722 725 if isinstance(input_val, unicode):
723 726 input_val = input_val.encode('utf8')
724 727 settings[name] = asbool(input_val)
725 728 return settings[name]
726 729
727 730
728 731 def _list_setting(settings, name, default):
729 732 raw_value = settings.get(name, default)
730 733
731 734 old_separator = ','
732 735 if old_separator in raw_value:
733 736 # If we get a comma separated list, pass it to our own function.
734 737 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
735 738 else:
736 739 # Otherwise we assume it uses pyramids space/newline separation.
737 740 settings[name] = aslist(raw_value)
738 741 return settings[name]
739 742
740 743
741 744 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
742 745 value = settings.get(name, default)
743 746
744 747 if default_when_empty and not value:
745 748 # use default value when value is empty
746 749 value = default
747 750
748 751 if lower:
749 752 value = value.lower()
750 753 settings[name] = value
751 754 return settings[name]
752 755
753 756
754 757 def _substitute_values(mapping, substitutions):
755 758 result = {}
756 759
757 760 try:
758 761 for key, value in mapping.items():
759 762 # initialize without substitution first
760 763 result[key] = value
761 764
762 765 # Note: Cannot use regular replacements, since they would clash
763 766 # with the implementation of ConfigParser. Using "format" instead.
764 767 try:
765 768 result[key] = value.format(**substitutions)
766 769 except KeyError as e:
767 770 env_var = '{}'.format(e.args[0])
768 771
769 772 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
770 773 'Make sure your environment has {var} set, or remove this ' \
771 774 'variable from config file'.format(key=key, var=env_var)
772 775
773 776 if env_var.startswith('ENV_'):
774 777 raise ValueError(msg)
775 778 else:
776 779 log.warning(msg)
777 780
778 781 except ValueError as e:
779 782 log.warning('Failed to substitute ENV variable: %s', e)
780 783 result = mapping
781 784
782 785 return result
@@ -1,171 +1,186 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import sys
22 22 import logging
23 23
24 24
25 25 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
26 26
27 27 # Sequences
28 28 RESET_SEQ = "\033[0m"
29 29 COLOR_SEQ = "\033[0;%dm"
30 30 BOLD_SEQ = "\033[1m"
31 31
32 32 COLORS = {
33 33 'CRITICAL': MAGENTA,
34 34 'ERROR': RED,
35 35 'WARNING': CYAN,
36 36 'INFO': GREEN,
37 37 'DEBUG': BLUE,
38 38 'SQL': YELLOW
39 39 }
40 40
41 41
42 42 def one_space_trim(s):
43 43 if s.find(" ") == -1:
44 44 return s
45 45 else:
46 46 s = s.replace(' ', ' ')
47 47 return one_space_trim(s)
48 48
49 49
50 50 def format_sql(sql):
51 51 sql = sql.replace('\n', '')
52 52 sql = one_space_trim(sql)
53 53 sql = sql\
54 54 .replace(',', ',\n\t')\
55 55 .replace('SELECT', '\n\tSELECT \n\t')\
56 56 .replace('UPDATE', '\n\tUPDATE \n\t')\
57 57 .replace('DELETE', '\n\tDELETE \n\t')\
58 58 .replace('FROM', '\n\tFROM')\
59 59 .replace('ORDER BY', '\n\tORDER BY')\
60 60 .replace('LIMIT', '\n\tLIMIT')\
61 61 .replace('WHERE', '\n\tWHERE')\
62 62 .replace('AND', '\n\tAND')\
63 63 .replace('LEFT', '\n\tLEFT')\
64 64 .replace('INNER', '\n\tINNER')\
65 65 .replace('INSERT', '\n\tINSERT')\
66 66 .replace('DELETE', '\n\tDELETE')
67 67 return sql
68 68
69 69
70 70 class ExceptionAwareFormatter(logging.Formatter):
71 71 """
72 72 Extended logging formatter which prints out remote tracebacks.
73 73 """
74 74
75 75 def formatException(self, ei):
76 76 ex_type, ex_value, ex_tb = ei
77 77
78 78 local_tb = logging.Formatter.formatException(self, ei)
79 79 if hasattr(ex_value, '_vcs_server_traceback'):
80 80
81 81 def formatRemoteTraceback(remote_tb_lines):
82 82 result = ["\n +--- This exception occured remotely on VCSServer - Remote traceback:\n\n"]
83 83 result.append(remote_tb_lines)
84 84 result.append("\n +--- End of remote traceback\n")
85 85 return result
86 86
87 87 try:
88 88 if ex_type is not None and ex_value is None and ex_tb is None:
89 89 # possible old (3.x) call syntax where caller is only
90 90 # providing exception object
91 91 if type(ex_type) is not type:
92 92 raise TypeError(
93 93 "invalid argument: ex_type should be an exception "
94 94 "type, or just supply no arguments at all")
95 95 if ex_type is None and ex_tb is None:
96 96 ex_type, ex_value, ex_tb = sys.exc_info()
97 97
98 98 remote_tb = getattr(ex_value, "_vcs_server_traceback", None)
99 99
100 100 if remote_tb:
101 101 remote_tb = formatRemoteTraceback(remote_tb)
102 102 return local_tb + ''.join(remote_tb)
103 103 finally:
104 104 # clean up cycle to traceback, to allow proper GC
105 105 del ex_type, ex_value, ex_tb
106 106
107 107 return local_tb
108 108
109 109
110 110 class ColorFormatter(ExceptionAwareFormatter):
111 111
112 112 def format(self, record):
113 113 """
114 114 Changes record's levelname to use with COLORS enum
115 115 """
116 116
117 117 levelname = record.levelname
118 118 start = COLOR_SEQ % (COLORS[levelname])
119 119 def_record = logging.Formatter.format(self, record)
120 120 end = RESET_SEQ
121 121
122 122 colored_record = ''.join([start, def_record, end])
123 123 return colored_record
124 124
125 125
126 126 def _inject_req_id(record):
127 127 from pyramid.threadlocal import get_current_request
128 dummy = '00000000-0000-0000-0000-000000000000'
129 req_id = None
130
128 131 req = get_current_request()
129 dummy = '00000000-0000-0000-0000-000000000000'
130 req_id = 'req_id:%-36s' % (getattr(req, 'req_id', dummy))
132 if req:
133 req_id = getattr(req, 'req_id', None)
134
135 req_id = 'req_id:%-36s' % (req_id or dummy)
131 136 record.req_id = req_id
132 137
133 138
139 def _add_log_to_debug_bucket(formatted_record):
140 from pyramid.threadlocal import get_current_request
141 req = get_current_request()
142 if req:
143 req.req_id_bucket.append(formatted_record)
144
145
134 146 class RequestTrackingFormatter(ExceptionAwareFormatter):
135 147 def format(self, record):
136 148 _inject_req_id(record)
137 149 def_record = logging.Formatter.format(self, record)
150 _add_log_to_debug_bucket(def_record)
138 151 return def_record
139 152
140 153
141 154 class ColorRequestTrackingFormatter(ColorFormatter):
155
142 156 def format(self, record):
143 157 """
144 158 Changes record's levelname to use with COLORS enum
145 159 """
146 160 _inject_req_id(record)
147 161 levelname = record.levelname
148 162 start = COLOR_SEQ % (COLORS[levelname])
149 163 def_record = logging.Formatter.format(self, record)
150 164 end = RESET_SEQ
151 165
152 166 colored_record = ''.join([start, def_record, end])
167 _add_log_to_debug_bucket(def_record)
153 168 return colored_record
154 169
155 170
156 171 class ColorFormatterSql(logging.Formatter):
157 172
158 173 def format(self, record):
159 174 """
160 175 Changes record's levelname to use with COLORS enum
161 176 """
162 177
163 178 start = COLOR_SEQ % (COLORS['SQL'])
164 179 def_record = format_sql(logging.Formatter.format(self, record))
165 180 end = RESET_SEQ
166 181
167 182 colored_record = ''.join([start, def_record, end])
168 183 return colored_record
169 184
170 185 # marcink: needs to stay with this name for backward .ini compatability
171 186 Pyro4AwareFormatter = ExceptionAwareFormatter
@@ -1,29 +1,38 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 from uuid import uuid4
22 22 from pyramid.decorator import reify
23 23 from pyramid.request import Request as _Request
24 24
25 25
26 26 class Request(_Request):
27 _req_id_bucket = list()
28
27 29 @reify
28 30 def req_id(self):
29 31 return str(uuid4())
32
33 @property
34 def req_id_bucket(self):
35 return self._req_id_bucket
36
37 def req_id_records_init(self):
38 self._req_id_bucket = list()
@@ -1,397 +1,405 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 import io
21 21 import shlex
22 22
23 23 import math
24 24 import re
25 25 import os
26 26 import datetime
27 27 import logging
28 28 import Queue
29 29 import subprocess32
30 30
31 31
32 32 from dateutil.parser import parse
33 33 from pyramid.threadlocal import get_current_request
34 34 from pyramid.interfaces import IRoutesMapper
35 35 from pyramid.settings import asbool
36 36 from pyramid.path import AssetResolver
37 37 from threading import Thread
38 38
39 39 from rhodecode.translation import _ as tsf
40 40 from rhodecode.config.jsroutes import generate_jsroutes_content
41 41 from rhodecode.lib import auth
42 42 from rhodecode.lib.base import get_auth_user
43 43
44 44 import rhodecode
45 45
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 def add_renderer_globals(event):
51 51 from rhodecode.lib import helpers
52 52
53 53 # TODO: When executed in pyramid view context the request is not available
54 54 # in the event. Find a better solution to get the request.
55 55 request = event['request'] or get_current_request()
56 56
57 57 # Add Pyramid translation as '_' to context
58 58 event['_'] = request.translate
59 59 event['_ungettext'] = request.plularize
60 60 event['h'] = helpers
61 61
62 62
63 63 def add_localizer(event):
64 64 request = event.request
65 65 localizer = request.localizer
66 66
67 67 def auto_translate(*args, **kwargs):
68 68 return localizer.translate(tsf(*args, **kwargs))
69 69
70 70 request.translate = auto_translate
71 71 request.plularize = localizer.pluralize
72 72
73 73
74 74 def set_user_lang(event):
75 75 request = event.request
76 76 cur_user = getattr(request, 'user', None)
77 77
78 78 if cur_user:
79 79 user_lang = cur_user.get_instance().user_data.get('language')
80 80 if user_lang:
81 81 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
82 82 event.request._LOCALE_ = user_lang
83 83
84 84
85 85 def add_request_user_context(event):
86 86 """
87 87 Adds auth user into request context
88 88 """
89 89 request = event.request
90 90 # access req_id as soon as possible
91 91 req_id = request.req_id
92 92
93 93 if hasattr(request, 'vcs_call'):
94 94 # skip vcs calls
95 95 return
96 96
97 97 if hasattr(request, 'rpc_method'):
98 98 # skip api calls
99 99 return
100 100
101 101 auth_user, auth_token = get_auth_user(request)
102 102 request.user = auth_user
103 103 request.user_auth_token = auth_token
104 104 request.environ['rc_auth_user'] = auth_user
105 105 request.environ['rc_auth_user_id'] = auth_user.user_id
106 106 request.environ['rc_req_id'] = req_id
107 107
108 108
109 def reset_log_bucket(event):
110 """
111 reset the log bucket on new request
112 """
113 request = event.request
114 request.req_id_records_init()
115
116
109 117 def scan_repositories_if_enabled(event):
110 118 """
111 119 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
112 120 does a repository scan if enabled in the settings.
113 121 """
114 122 settings = event.app.registry.settings
115 123 vcs_server_enabled = settings['vcs.server.enable']
116 124 import_on_startup = settings['startup.import_repos']
117 125 if vcs_server_enabled and import_on_startup:
118 126 from rhodecode.model.scm import ScmModel
119 127 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
120 128 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
121 129 repo2db_mapper(repositories, remove_obsolete=False)
122 130
123 131
124 132 def write_metadata_if_needed(event):
125 133 """
126 134 Writes upgrade metadata
127 135 """
128 136 import rhodecode
129 137 from rhodecode.lib import system_info
130 138 from rhodecode.lib import ext_json
131 139
132 140 fname = '.rcmetadata.json'
133 141 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
134 142 metadata_destination = os.path.join(ini_loc, fname)
135 143
136 144 def get_update_age():
137 145 now = datetime.datetime.utcnow()
138 146
139 147 with open(metadata_destination, 'rb') as f:
140 148 data = ext_json.json.loads(f.read())
141 149 if 'created_on' in data:
142 150 update_date = parse(data['created_on'])
143 151 diff = now - update_date
144 152 return diff.total_seconds() / 60.0
145 153
146 154 return 0
147 155
148 156 def write():
149 157 configuration = system_info.SysInfo(
150 158 system_info.rhodecode_config)()['value']
151 159 license_token = configuration['config']['license_token']
152 160
153 161 setup = dict(
154 162 workers=configuration['config']['server:main'].get(
155 163 'workers', '?'),
156 164 worker_type=configuration['config']['server:main'].get(
157 165 'worker_class', 'sync'),
158 166 )
159 167 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
160 168 del dbinfo['url']
161 169
162 170 metadata = dict(
163 171 desc='upgrade metadata info',
164 172 license_token=license_token,
165 173 created_on=datetime.datetime.utcnow().isoformat(),
166 174 usage=system_info.SysInfo(system_info.usage_info)()['value'],
167 175 platform=system_info.SysInfo(system_info.platform_type)()['value'],
168 176 database=dbinfo,
169 177 cpu=system_info.SysInfo(system_info.cpu)()['value'],
170 178 memory=system_info.SysInfo(system_info.memory)()['value'],
171 179 setup=setup
172 180 )
173 181
174 182 with open(metadata_destination, 'wb') as f:
175 183 f.write(ext_json.json.dumps(metadata))
176 184
177 185 settings = event.app.registry.settings
178 186 if settings.get('metadata.skip'):
179 187 return
180 188
181 189 # only write this every 24h, workers restart caused unwanted delays
182 190 try:
183 191 age_in_min = get_update_age()
184 192 except Exception:
185 193 age_in_min = 0
186 194
187 195 if age_in_min > 60 * 60 * 24:
188 196 return
189 197
190 198 try:
191 199 write()
192 200 except Exception:
193 201 pass
194 202
195 203
196 204 def write_usage_data(event):
197 205 import rhodecode
198 206 from rhodecode.lib import system_info
199 207 from rhodecode.lib import ext_json
200 208
201 209 settings = event.app.registry.settings
202 210 instance_tag = settings.get('metadata.write_usage_tag')
203 211 if not settings.get('metadata.write_usage'):
204 212 return
205 213
206 214 def get_update_age(dest_file):
207 215 now = datetime.datetime.utcnow()
208 216
209 217 with open(dest_file, 'rb') as f:
210 218 data = ext_json.json.loads(f.read())
211 219 if 'created_on' in data:
212 220 update_date = parse(data['created_on'])
213 221 diff = now - update_date
214 222 return math.ceil(diff.total_seconds() / 60.0)
215 223
216 224 return 0
217 225
218 226 utc_date = datetime.datetime.utcnow()
219 227 hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.))
220 228 fname = '.rc_usage_{date.year}{date.month:02d}{date.day:02d}_{hour}.json'.format(
221 229 date=utc_date, hour=hour_quarter)
222 230 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
223 231
224 232 usage_dir = os.path.join(ini_loc, '.rcusage')
225 233 if not os.path.isdir(usage_dir):
226 234 os.makedirs(usage_dir)
227 235 usage_metadata_destination = os.path.join(usage_dir, fname)
228 236
229 237 try:
230 238 age_in_min = get_update_age(usage_metadata_destination)
231 239 except Exception:
232 240 age_in_min = 0
233 241
234 242 # write every 6th hour
235 243 if age_in_min and age_in_min < 60 * 6:
236 244 log.debug('Usage file created %s minutes ago, skipping (threshold: %s minutes)...',
237 245 age_in_min, 60 * 6)
238 246 return
239 247
240 248 def write(dest_file):
241 249 configuration = system_info.SysInfo(system_info.rhodecode_config)()['value']
242 250 license_token = configuration['config']['license_token']
243 251
244 252 metadata = dict(
245 253 desc='Usage data',
246 254 instance_tag=instance_tag,
247 255 license_token=license_token,
248 256 created_on=datetime.datetime.utcnow().isoformat(),
249 257 usage=system_info.SysInfo(system_info.usage_info)()['value'],
250 258 )
251 259
252 260 with open(dest_file, 'wb') as f:
253 261 f.write(ext_json.json.dumps(metadata, indent=2, sort_keys=True))
254 262
255 263 try:
256 264 log.debug('Writing usage file at: %s', usage_metadata_destination)
257 265 write(usage_metadata_destination)
258 266 except Exception:
259 267 pass
260 268
261 269
262 270 def write_js_routes_if_enabled(event):
263 271 registry = event.app.registry
264 272
265 273 mapper = registry.queryUtility(IRoutesMapper)
266 274 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
267 275
268 276 def _extract_route_information(route):
269 277 """
270 278 Convert a route into tuple(name, path, args), eg:
271 279 ('show_user', '/profile/%(username)s', ['username'])
272 280 """
273 281
274 282 routepath = route.pattern
275 283 pattern = route.pattern
276 284
277 285 def replace(matchobj):
278 286 if matchobj.group(1):
279 287 return "%%(%s)s" % matchobj.group(1).split(':')[0]
280 288 else:
281 289 return "%%(%s)s" % matchobj.group(2)
282 290
283 291 routepath = _argument_prog.sub(replace, routepath)
284 292
285 293 if not routepath.startswith('/'):
286 294 routepath = '/'+routepath
287 295
288 296 return (
289 297 route.name,
290 298 routepath,
291 299 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
292 300 for arg in _argument_prog.findall(pattern)]
293 301 )
294 302
295 303 def get_routes():
296 304 # pyramid routes
297 305 for route in mapper.get_routes():
298 306 if not route.name.startswith('__'):
299 307 yield _extract_route_information(route)
300 308
301 309 if asbool(registry.settings.get('generate_js_files', 'false')):
302 310 static_path = AssetResolver().resolve('rhodecode:public').abspath()
303 311 jsroutes = get_routes()
304 312 jsroutes_file_content = generate_jsroutes_content(jsroutes)
305 313 jsroutes_file_path = os.path.join(
306 314 static_path, 'js', 'rhodecode', 'routes.js')
307 315
308 316 try:
309 317 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
310 318 f.write(jsroutes_file_content)
311 319 except Exception:
312 320 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
313 321
314 322
315 323 class Subscriber(object):
316 324 """
317 325 Base class for subscribers to the pyramid event system.
318 326 """
319 327 def __call__(self, event):
320 328 self.run(event)
321 329
322 330 def run(self, event):
323 331 raise NotImplementedError('Subclass has to implement this.')
324 332
325 333
326 334 class AsyncSubscriber(Subscriber):
327 335 """
328 336 Subscriber that handles the execution of events in a separate task to not
329 337 block the execution of the code which triggers the event. It puts the
330 338 received events into a queue from which the worker process takes them in
331 339 order.
332 340 """
333 341 def __init__(self):
334 342 self._stop = False
335 343 self._eventq = Queue.Queue()
336 344 self._worker = self.create_worker()
337 345 self._worker.start()
338 346
339 347 def __call__(self, event):
340 348 self._eventq.put(event)
341 349
342 350 def create_worker(self):
343 351 worker = Thread(target=self.do_work)
344 352 worker.daemon = True
345 353 return worker
346 354
347 355 def stop_worker(self):
348 356 self._stop = False
349 357 self._eventq.put(None)
350 358 self._worker.join()
351 359
352 360 def do_work(self):
353 361 while not self._stop:
354 362 event = self._eventq.get()
355 363 if event is not None:
356 364 self.run(event)
357 365
358 366
359 367 class AsyncSubprocessSubscriber(AsyncSubscriber):
360 368 """
361 369 Subscriber that uses the subprocess32 module to execute a command if an
362 370 event is received. Events are handled asynchronously::
363 371
364 372 subscriber = AsyncSubprocessSubscriber('ls -la', timeout=10)
365 373 subscriber(dummyEvent) # running __call__(event)
366 374
367 375 """
368 376
369 377 def __init__(self, cmd, timeout=None):
370 378 if not isinstance(cmd, (list, tuple)):
371 379 cmd = shlex.split(cmd)
372 380 super(AsyncSubprocessSubscriber, self).__init__()
373 381 self._cmd = cmd
374 382 self._timeout = timeout
375 383
376 384 def run(self, event):
377 385 cmd = self._cmd
378 386 timeout = self._timeout
379 387 log.debug('Executing command %s.', cmd)
380 388
381 389 try:
382 390 output = subprocess32.check_output(
383 391 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
384 392 log.debug('Command finished %s', cmd)
385 393 if output:
386 394 log.debug('Command output: %s', output)
387 395 except subprocess32.TimeoutExpired as e:
388 396 log.exception('Timeout while executing command.')
389 397 if e.output:
390 398 log.error('Command output: %s', e.output)
391 399 except subprocess32.CalledProcessError as e:
392 400 log.exception('Error while executing command.')
393 401 if e.output:
394 402 log.error('Command output: %s', e.output)
395 403 except Exception:
396 404 log.exception(
397 405 'Exception while executing command %s.', cmd)
@@ -1,94 +1,112 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html>
3 3 <html xmlns="http://www.w3.org/1999/xhtml">
4 4 <head>
5 5 <title>Error - ${c.error_message}</title>
6 6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 7 <meta name="robots" content="index, nofollow"/>
8 8
9 9 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
10 10 %if c.redirect_time:
11 11 <meta http-equiv="refresh" content="${c.redirect_time}; url=${c.url_redirect}"/>
12 12 %endif
13 13
14 14 <link id="favicon" rel="shortcut icon" type="image/png" href="data:image/png;base64,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">
15 15 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
16 16 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
17 17
18 18 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
19 19 <style>body { background:#eeeeee; }</style>
20 20 <script type="text/javascript">
21 21 // register templateContext to pass template variables to JS
22 22 var templateContext = {timeago: {}};
23 23 </script>
24 24 <%include file="/base/plugins_base.mako"/>
25 25 <script type="text/javascript" src="${h.asset('js/scripts.min.js', ver=c.rhodecode_version_hash)}"></script>
26 26 </head>
27 27 <body>
28 28
29 29 <div class="wrapper error_page">
30 30 <div class="sidebar">
31 31 <a href="${h.route_path('home')}"><img class="error-page-logo" src="${h.asset('images/RhodeCode_Logo_Black.png')}" alt="RhodeCode"/></a>
32 32 </div>
33 33 <div class="main-content">
34 34 <h1>
35 35 <span class="error-branding">
36 36 ${h.branding(c.rhodecode_name)}
37 37 </span><br/>
38 38 ${c.error_message}
39 39 <br/>
40 40 <span class="error_message">${c.error_explanation}</span>
41 41 </h1>
42 42 % if c.messages:
43 43 % for message in c.messages:
44 44 <div class="alert alert-${message.category}">${message}</div>
45 45 % endfor
46 46 % endif
47 47 %if c.redirect_time:
48 48 <p>${_('You will be redirected to %s in %s seconds') % (c.redirect_module,c.redirect_time)}</p>
49 49 %endif
50 50 <div class="inner-column">
51 51 <h4>Possible Causes</h4>
52 52 <ul>
53 53 % if c.causes:
54 54 %for cause in c.causes:
55 55 <li>${cause}</li>
56 56 %endfor
57 57 %else:
58 58 <li>The resource may have been deleted.</li>
59 59 <li>You may not have access to this repository.</li>
60 60 <li>The link may be incorrect.</li>
61 61 %endif
62 62 </ul>
63 63 </div>
64 64 <div class="inner-column">
65 65 <h4>Support</h4>
66 66 <p>For help and support, go to the <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support Page')}</a>.
67 67 It may be useful to include your log file; see the log file locations <a href="${h.route_url('enterprise_log_file_locations')}">here</a>.
68 68 </p>
69 69
70 70 </div>
71 71 <div class="inner-column">
72 72 <h4>Documentation</h4>
73 73 <p>For more information, see <a href="${h.route_url('enterprise_docs')}">docs.rhodecode.com</a>.</p>
74 74 </div>
75 75 </div>
76 76
77 77 % if c.show_exception_id:
78 78 <div class="sidebar" style="width: 130px">
79 79
80 80 </div>
81 81 <div class="main-content">
82 82 <p>
83 83 <strong>Exception ID: <code><a href="${c.exception_id_url}">${c.exception_id}</a></code> </strong> <br/>
84 84
85 85 Super-admins can see details of the above error in the exception tracker found under
86 86 <a href="${h.route_url('admin_settings_exception_tracker')}">admin > settings > exception tracker</a>.
87
88 % if c.exception_debug:
89 <pre>
90 <strong>DEBUG MODE ON FOR EXCEPTION: ${c.exception_id}</strong>
91 <strong>REQUEST_ID: ${getattr(request, 'req_id', None)}</strong>
92 ----------------
93 debug mode is controlled by
94 ${c.exception_config_ini}
95 file settings:
96
97 debug = true
98 ----------------
99
100 % for rec in getattr(request, 'req_id_bucket', []):
101 ${rec}
102 % endfor
103 </pre>
104 % endif
87 105 </p>
88 106 </div>
89 107 % endif
90 108 </div>
91 109
92 110 </body>
93 111
94 112 </html>
@@ -1,122 +1,124 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 from pyramid.httpexceptions import HTTPException, HTTPBadRequest
24 24
25 25 from rhodecode.lib.middleware.vcs import (
26 26 detect_vcs_request, VCS_TYPE_KEY, VCS_TYPE_SKIP)
27 27
28 28
29 29 log = logging.getLogger(__name__)
30 30
31 31
32 32 def vcs_detection_tween_factory(handler, registry):
33 33
34 34 def vcs_detection_tween(request):
35 35 """
36 36 Do detection of vcs type, and save results for other layers to re-use
37 37 this information
38 38 """
39 39 vcs_server_enabled = request.registry.settings.get('vcs.server.enable')
40 40 vcs_handler = vcs_server_enabled and detect_vcs_request(
41 41 request.environ, request.registry.settings.get('vcs.backends'))
42 42
43 43 if vcs_handler:
44 44 # save detected VCS type for later re-use
45 45 request.environ[VCS_TYPE_KEY] = vcs_handler.SCM
46 46 request.vcs_call = vcs_handler.SCM
47 47
48 48 log.debug('Processing request with `%s` handler', handler)
49 49 return handler(request)
50 50
51 51 # mark that we didn't detect an VCS, and we can skip detection later on
52 52 request.environ[VCS_TYPE_KEY] = VCS_TYPE_SKIP
53 53
54 54 log.debug('Processing request with `%s` handler', handler)
55 55 return handler(request)
56 56
57 57 return vcs_detection_tween
58 58
59 59
60 60 def junk_encoding_detector(request):
61 61 """
62 62 Detect bad encoded GET params, and fail immediately with BadRequest
63 63 """
64 64
65 65 try:
66 66 request.GET.get("", None)
67 67 except UnicodeDecodeError:
68 68 raise HTTPBadRequest("Invalid bytes in query string.")
69 69
70 70
71 71 def bad_url_data_detector(request):
72 72 """
73 73 Detect invalid bytes in a path.
74 74 """
75 75 try:
76 76 request.path_info
77 77 except UnicodeDecodeError:
78 78 raise HTTPBadRequest("Invalid bytes in URL.")
79 79
80 80
81 81 def junk_form_data_detector(request):
82 82 """
83 83 Detect bad encoded POST params, and fail immediately with BadRequest
84 84 """
85 85
86 86 if request.method == "POST":
87 87 try:
88 88 request.POST.get("", None)
89 89 except ValueError:
90 90 raise HTTPBadRequest("Invalid bytes in form data.")
91 91
92 92
93 93 def sanity_check_factory(handler, registry):
94 94 def sanity_check(request):
95 95 log.debug('Checking current URL sanity for bad data')
96 96 try:
97 97 junk_encoding_detector(request)
98 98 bad_url_data_detector(request)
99 99 junk_form_data_detector(request)
100 100 except HTTPException as exc:
101 101 return exc
102 102
103 103 return handler(request)
104 104
105 105 return sanity_check
106 106
107 107
108 108 def includeme(config):
109 109 config.add_subscriber('rhodecode.subscribers.add_renderer_globals',
110 110 'pyramid.events.BeforeRender')
111 111 config.add_subscriber('rhodecode.subscribers.set_user_lang',
112 112 'pyramid.events.NewRequest')
113 113 config.add_subscriber('rhodecode.subscribers.add_localizer',
114 114 'pyramid.events.NewRequest')
115 config.add_subscriber('rhodecode.subscribers.reset_log_bucket',
116 'pyramid.events.NewRequest')
115 117 config.add_subscriber('rhodecode.subscribers.add_request_user_context',
116 118 'pyramid.events.ContextFound')
117 119 config.add_tween('rhodecode.tweens.vcs_detection_tween_factory')
118 120 config.add_tween('rhodecode.tweens.sanity_check_factory')
119 121
120 122 # This needs to be the LAST item
121 123 config.add_tween('rhodecode.lib.middleware.request_wrapper.RequestWrapperTween')
122 124 log.debug('configured all tweens')
General Comments 0
You need to be logged in to leave comments. Login now