##// END OF EJS Templates
metrics: further adjustments....
super-admin -
r4796:317404db default
parent child Browse files
Show More
@@ -1,796 +1,796 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 from rhodecode.lib.statsd_client import StatsdClient
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 # init and bootstrap StatsdClient
97 97 StatsdClient.setup(settings)
98 98
99 99 debug = asbool(global_config.get('debug'))
100 100 if debug:
101 101 enable_debug()
102 102
103 103 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
104 104
105 105 global_config = _substitute_values(global_config, environ)
106 106 settings = _substitute_values(settings, environ)
107 107
108 108 sanitize_settings_and_apply_defaults(global_config, settings)
109 109
110 110 config = Configurator(settings=settings)
111 111 # Init our statsd at very start
112 112 config.registry.statsd = StatsdClient.statsd
113 113
114 114 # Apply compatibility patches
115 115 patches.inspect_getargspec()
116 116
117 117 load_pyramid_environment(global_config, settings)
118 118
119 119 # Static file view comes first
120 120 includeme_first(config)
121 121
122 122 includeme(config)
123 123
124 124 pyramid_app = config.make_wsgi_app()
125 125 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
126 126 pyramid_app.config = config
127 127
128 128 config.configure_celery(global_config['__file__'])
129 129
130 130 # creating the app uses a connection - return it after we are done
131 131 meta.Session.remove()
132 132 statsd = StatsdClient.statsd
133 133
134 134 total_time = time.time() - start_time
135 135 log.info('Pyramid app `%s` created and configured in %.2fs',
136 136 pyramid_app.func_name, total_time)
137 137 if statsd:
138 138 elapsed_time_ms = 1000.0 * total_time
139 139 statsd.timing('rhodecode_app_bootstrap_timing', elapsed_time_ms, tags=[
140 140 "pyramid_app:{}".format(pyramid_app.func_name)
141 141 ])
142 142 return pyramid_app
143 143
144 144
145 145 def not_found_view(request):
146 146 """
147 147 This creates the view which should be registered as not-found-view to
148 148 pyramid.
149 149 """
150 150
151 151 if not getattr(request, 'vcs_call', None):
152 152 # handle like regular case with our error_handler
153 153 return error_handler(HTTPNotFound(), request)
154 154
155 155 # handle not found view as a vcs call
156 156 settings = request.registry.settings
157 157 ae_client = getattr(request, 'ae_client', None)
158 158 vcs_app = VCSMiddleware(
159 159 HTTPNotFound(), request.registry, settings,
160 160 appenlight_client=ae_client)
161 161
162 162 return wsgiapp(vcs_app)(None, request)
163 163
164 164
165 165 def error_handler(exception, request):
166 166 import rhodecode
167 167 from rhodecode.lib import helpers
168 168 from rhodecode.lib.utils2 import str2bool
169 169
170 170 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
171 171
172 172 base_response = HTTPInternalServerError()
173 173 # prefer original exception for the response since it may have headers set
174 174 if isinstance(exception, HTTPException):
175 175 base_response = exception
176 176 elif isinstance(exception, VCSCommunicationError):
177 177 base_response = VCSServerUnavailable()
178 178
179 179 if is_http_error(base_response):
180 180 log.exception(
181 181 'error occurred handling this request for path: %s', request.path)
182 182
183 183 statsd = request.registry.statsd
184 184 if statsd and base_response.status_code > 499:
185 statsd.incr('rhodecode_exception')
185 statsd.incr('rhodecode_exception', tags=["code:{}".format(base_response.status_code)])
186 186
187 187 error_explanation = base_response.explanation or str(base_response)
188 188 if base_response.status_code == 404:
189 189 error_explanation += " Optionally you don't have permission to access this page."
190 190 c = AttributeDict()
191 191 c.error_message = base_response.status
192 192 c.error_explanation = error_explanation
193 193 c.visual = AttributeDict()
194 194
195 195 c.visual.rhodecode_support_url = (
196 196 request.registry.settings.get('rhodecode_support_url') or
197 197 request.route_url('rhodecode_support')
198 198 )
199 199 c.redirect_time = 0
200 200 c.rhodecode_name = rhodecode_title
201 201 if not c.rhodecode_name:
202 202 c.rhodecode_name = 'Rhodecode'
203 203
204 204 c.causes = []
205 205 if is_http_error(base_response):
206 206 c.causes.append('Server is overloaded.')
207 207 c.causes.append('Server database connection is lost.')
208 208 c.causes.append('Server expected unhandled error.')
209 209
210 210 if hasattr(base_response, 'causes'):
211 211 c.causes = base_response.causes
212 212
213 213 c.messages = helpers.flash.pop_messages(request=request)
214 214
215 215 exc_info = sys.exc_info()
216 216 c.exception_id = id(exc_info)
217 217 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
218 218 or base_response.status_code > 499
219 219 c.exception_id_url = request.route_url(
220 220 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
221 221
222 222 if c.show_exception_id:
223 223 store_exception(c.exception_id, exc_info)
224 224 c.exception_debug = str2bool(rhodecode.CONFIG.get('debug'))
225 225 c.exception_config_ini = rhodecode.CONFIG.get('__file__')
226 226
227 227 response = render_to_response(
228 228 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
229 229 response=base_response)
230 230
231 231 return response
232 232
233 233
234 234 def includeme_first(config):
235 235 # redirect automatic browser favicon.ico requests to correct place
236 236 def favicon_redirect(context, request):
237 237 return HTTPFound(
238 238 request.static_path('rhodecode:public/images/favicon.ico'))
239 239
240 240 config.add_view(favicon_redirect, route_name='favicon')
241 241 config.add_route('favicon', '/favicon.ico')
242 242
243 243 def robots_redirect(context, request):
244 244 return HTTPFound(
245 245 request.static_path('rhodecode:public/robots.txt'))
246 246
247 247 config.add_view(robots_redirect, route_name='robots')
248 248 config.add_route('robots', '/robots.txt')
249 249
250 250 config.add_static_view(
251 251 '_static/deform', 'deform:static')
252 252 config.add_static_view(
253 253 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
254 254
255 255
256 256 def includeme(config, auth_resources=None):
257 257 from rhodecode.lib.celerylib.loader import configure_celery
258 258 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
259 259 settings = config.registry.settings
260 260 config.set_request_factory(Request)
261 261
262 262 # plugin information
263 263 config.registry.rhodecode_plugins = collections.OrderedDict()
264 264
265 265 config.add_directive(
266 266 'register_rhodecode_plugin', register_rhodecode_plugin)
267 267
268 268 config.add_directive('configure_celery', configure_celery)
269 269
270 270 if asbool(settings.get('appenlight', 'false')):
271 271 config.include('appenlight_client.ext.pyramid_tween')
272 272
273 273 load_all = should_load_all()
274 274
275 275 # Includes which are required. The application would fail without them.
276 276 config.include('pyramid_mako')
277 277 config.include('rhodecode.lib.rc_beaker')
278 278 config.include('rhodecode.lib.rc_cache')
279 279 config.include('rhodecode.apps._base.navigation')
280 280 config.include('rhodecode.apps._base.subscribers')
281 281 config.include('rhodecode.tweens')
282 282 config.include('rhodecode.authentication')
283 283
284 284 if load_all:
285 285 ce_auth_resources = [
286 286 'rhodecode.authentication.plugins.auth_crowd',
287 287 'rhodecode.authentication.plugins.auth_headers',
288 288 'rhodecode.authentication.plugins.auth_jasig_cas',
289 289 'rhodecode.authentication.plugins.auth_ldap',
290 290 'rhodecode.authentication.plugins.auth_pam',
291 291 'rhodecode.authentication.plugins.auth_rhodecode',
292 292 'rhodecode.authentication.plugins.auth_token',
293 293 ]
294 294
295 295 # load CE authentication plugins
296 296
297 297 if auth_resources:
298 298 ce_auth_resources.extend(auth_resources)
299 299
300 300 for resource in ce_auth_resources:
301 301 config.include(resource)
302 302
303 303 # Auto discover authentication plugins and include their configuration.
304 304 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
305 305 from rhodecode.authentication import discover_legacy_plugins
306 306 discover_legacy_plugins(config)
307 307
308 308 # apps
309 309 if load_all:
310 310 config.include('rhodecode.api')
311 311 config.include('rhodecode.apps._base')
312 312 config.include('rhodecode.apps.hovercards')
313 313 config.include('rhodecode.apps.ops')
314 314 config.include('rhodecode.apps.channelstream')
315 315 config.include('rhodecode.apps.file_store')
316 316 config.include('rhodecode.apps.admin')
317 317 config.include('rhodecode.apps.login')
318 318 config.include('rhodecode.apps.home')
319 319 config.include('rhodecode.apps.journal')
320 320
321 321 config.include('rhodecode.apps.repository')
322 322 config.include('rhodecode.apps.repo_group')
323 323 config.include('rhodecode.apps.user_group')
324 324 config.include('rhodecode.apps.search')
325 325 config.include('rhodecode.apps.user_profile')
326 326 config.include('rhodecode.apps.user_group_profile')
327 327 config.include('rhodecode.apps.my_account')
328 328 config.include('rhodecode.apps.gist')
329 329
330 330 config.include('rhodecode.apps.svn_support')
331 331 config.include('rhodecode.apps.ssh_support')
332 332 config.include('rhodecode.apps.debug_style')
333 333
334 334 if load_all:
335 335 config.include('rhodecode.integrations')
336 336
337 337 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
338 338 config.add_translation_dirs('rhodecode:i18n/')
339 339 settings['default_locale_name'] = settings.get('lang', 'en')
340 340
341 341 # Add subscribers.
342 342 if load_all:
343 343 config.add_subscriber(scan_repositories_if_enabled,
344 344 pyramid.events.ApplicationCreated)
345 345 config.add_subscriber(write_metadata_if_needed,
346 346 pyramid.events.ApplicationCreated)
347 347 config.add_subscriber(write_usage_data,
348 348 pyramid.events.ApplicationCreated)
349 349 config.add_subscriber(write_js_routes_if_enabled,
350 350 pyramid.events.ApplicationCreated)
351 351
352 352 # request custom methods
353 353 config.add_request_method(
354 354 'rhodecode.lib.partial_renderer.get_partial_renderer',
355 355 'get_partial_renderer')
356 356
357 357 config.add_request_method(
358 358 'rhodecode.lib.request_counter.get_request_counter',
359 359 'request_count')
360 360
361 361 # Set the authorization policy.
362 362 authz_policy = ACLAuthorizationPolicy()
363 363 config.set_authorization_policy(authz_policy)
364 364
365 365 # Set the default renderer for HTML templates to mako.
366 366 config.add_mako_renderer('.html')
367 367
368 368 config.add_renderer(
369 369 name='json_ext',
370 370 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
371 371
372 372 config.add_renderer(
373 373 name='string_html',
374 374 factory='rhodecode.lib.string_renderer.html')
375 375
376 376 # include RhodeCode plugins
377 377 includes = aslist(settings.get('rhodecode.includes', []))
378 378 for inc in includes:
379 379 config.include(inc)
380 380
381 381 # custom not found view, if our pyramid app doesn't know how to handle
382 382 # the request pass it to potential VCS handling ap
383 383 config.add_notfound_view(not_found_view)
384 384 if not settings.get('debugtoolbar.enabled', False):
385 385 # disabled debugtoolbar handle all exceptions via the error_handlers
386 386 config.add_view(error_handler, context=Exception)
387 387
388 388 # all errors including 403/404/50X
389 389 config.add_view(error_handler, context=HTTPError)
390 390
391 391
392 392 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
393 393 """
394 394 Apply outer WSGI middlewares around the application.
395 395 """
396 396 registry = config.registry
397 397 settings = registry.settings
398 398
399 399 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
400 400 pyramid_app = HttpsFixup(pyramid_app, settings)
401 401
402 402 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
403 403 pyramid_app, settings)
404 404 registry.ae_client = _ae_client
405 405
406 406 if settings['gzip_responses']:
407 407 pyramid_app = make_gzip_middleware(
408 408 pyramid_app, settings, compress_level=1)
409 409
410 410 # this should be the outer most middleware in the wsgi stack since
411 411 # middleware like Routes make database calls
412 412 def pyramid_app_with_cleanup(environ, start_response):
413 413 try:
414 414 return pyramid_app(environ, start_response)
415 415 finally:
416 416 # Dispose current database session and rollback uncommitted
417 417 # transactions.
418 418 meta.Session.remove()
419 419
420 420 # In a single threaded mode server, on non sqlite db we should have
421 421 # '0 Current Checked out connections' at the end of a request,
422 422 # if not, then something, somewhere is leaving a connection open
423 423 pool = meta.Base.metadata.bind.engine.pool
424 424 log.debug('sa pool status: %s', pool.status())
425 425 log.debug('Request processing finalized')
426 426
427 427 return pyramid_app_with_cleanup
428 428
429 429
430 430 def sanitize_settings_and_apply_defaults(global_config, settings):
431 431 """
432 432 Applies settings defaults and does all type conversion.
433 433
434 434 We would move all settings parsing and preparation into this place, so that
435 435 we have only one place left which deals with this part. The remaining parts
436 436 of the application would start to rely fully on well prepared settings.
437 437
438 438 This piece would later be split up per topic to avoid a big fat monster
439 439 function.
440 440 """
441 441
442 442 settings.setdefault('rhodecode.edition', 'Community Edition')
443 443 settings.setdefault('rhodecode.edition_id', 'CE')
444 444
445 445 if 'mako.default_filters' not in settings:
446 446 # set custom default filters if we don't have it defined
447 447 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
448 448 settings['mako.default_filters'] = 'h_filter'
449 449
450 450 if 'mako.directories' not in settings:
451 451 mako_directories = settings.setdefault('mako.directories', [
452 452 # Base templates of the original application
453 453 'rhodecode:templates',
454 454 ])
455 455 log.debug(
456 456 "Using the following Mako template directories: %s",
457 457 mako_directories)
458 458
459 459 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
460 460 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
461 461 raw_url = settings['beaker.session.url']
462 462 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
463 463 settings['beaker.session.url'] = 'redis://' + raw_url
464 464
465 465 # Default includes, possible to change as a user
466 466 pyramid_includes = settings.setdefault('pyramid.includes', [])
467 467 log.debug(
468 468 "Using the following pyramid.includes: %s",
469 469 pyramid_includes)
470 470
471 471 # TODO: johbo: Re-think this, usually the call to config.include
472 472 # should allow to pass in a prefix.
473 473 settings.setdefault('rhodecode.api.url', '/_admin/api')
474 474 settings.setdefault('__file__', global_config.get('__file__'))
475 475
476 476 # Sanitize generic settings.
477 477 _list_setting(settings, 'default_encoding', 'UTF-8')
478 478 _bool_setting(settings, 'is_test', 'false')
479 479 _bool_setting(settings, 'gzip_responses', 'false')
480 480
481 481 # Call split out functions that sanitize settings for each topic.
482 482 _sanitize_appenlight_settings(settings)
483 483 _sanitize_vcs_settings(settings)
484 484 _sanitize_cache_settings(settings)
485 485
486 486 # configure instance id
487 487 config_utils.set_instance_id(settings)
488 488
489 489 return settings
490 490
491 491
492 492 def enable_debug():
493 493 """
494 494 Helper to enable debug on running instance
495 495 :return:
496 496 """
497 497 import tempfile
498 498 import textwrap
499 499 import logging.config
500 500
501 501 ini_template = textwrap.dedent("""
502 502 #####################################
503 503 ### DEBUG LOGGING CONFIGURATION ####
504 504 #####################################
505 505 [loggers]
506 506 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
507 507
508 508 [handlers]
509 509 keys = console, console_sql
510 510
511 511 [formatters]
512 512 keys = generic, color_formatter, color_formatter_sql
513 513
514 514 #############
515 515 ## LOGGERS ##
516 516 #############
517 517 [logger_root]
518 518 level = NOTSET
519 519 handlers = console
520 520
521 521 [logger_sqlalchemy]
522 522 level = INFO
523 523 handlers = console_sql
524 524 qualname = sqlalchemy.engine
525 525 propagate = 0
526 526
527 527 [logger_beaker]
528 528 level = DEBUG
529 529 handlers =
530 530 qualname = beaker.container
531 531 propagate = 1
532 532
533 533 [logger_rhodecode]
534 534 level = DEBUG
535 535 handlers =
536 536 qualname = rhodecode
537 537 propagate = 1
538 538
539 539 [logger_ssh_wrapper]
540 540 level = DEBUG
541 541 handlers =
542 542 qualname = ssh_wrapper
543 543 propagate = 1
544 544
545 545 [logger_celery]
546 546 level = DEBUG
547 547 handlers =
548 548 qualname = celery
549 549
550 550
551 551 ##############
552 552 ## HANDLERS ##
553 553 ##############
554 554
555 555 [handler_console]
556 556 class = StreamHandler
557 557 args = (sys.stderr, )
558 558 level = DEBUG
559 559 formatter = color_formatter
560 560
561 561 [handler_console_sql]
562 562 # "level = DEBUG" logs SQL queries and results.
563 563 # "level = INFO" logs SQL queries.
564 564 # "level = WARN" logs neither. (Recommended for production systems.)
565 565 class = StreamHandler
566 566 args = (sys.stderr, )
567 567 level = WARN
568 568 formatter = color_formatter_sql
569 569
570 570 ################
571 571 ## FORMATTERS ##
572 572 ################
573 573
574 574 [formatter_generic]
575 575 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
576 576 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
577 577 datefmt = %Y-%m-%d %H:%M:%S
578 578
579 579 [formatter_color_formatter]
580 580 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
581 581 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
582 582 datefmt = %Y-%m-%d %H:%M:%S
583 583
584 584 [formatter_color_formatter_sql]
585 585 class = rhodecode.lib.logging_formatter.ColorFormatterSql
586 586 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
587 587 datefmt = %Y-%m-%d %H:%M:%S
588 588 """)
589 589
590 590 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
591 591 delete=False) as f:
592 592 log.info('Saved Temporary DEBUG config at %s', f.name)
593 593 f.write(ini_template)
594 594
595 595 logging.config.fileConfig(f.name)
596 596 log.debug('DEBUG MODE ON')
597 597 os.remove(f.name)
598 598
599 599
600 600 def _sanitize_appenlight_settings(settings):
601 601 _bool_setting(settings, 'appenlight', 'false')
602 602
603 603
604 604 def _sanitize_vcs_settings(settings):
605 605 """
606 606 Applies settings defaults and does type conversion for all VCS related
607 607 settings.
608 608 """
609 609 _string_setting(settings, 'vcs.svn.compatible_version', '')
610 610 _string_setting(settings, 'vcs.hooks.protocol', 'http')
611 611 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
612 612 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
613 613 _string_setting(settings, 'vcs.server', '')
614 614 _string_setting(settings, 'vcs.server.protocol', 'http')
615 615 _bool_setting(settings, 'startup.import_repos', 'false')
616 616 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
617 617 _bool_setting(settings, 'vcs.server.enable', 'true')
618 618 _bool_setting(settings, 'vcs.start_server', 'false')
619 619 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
620 620 _int_setting(settings, 'vcs.connection_timeout', 3600)
621 621
622 622 # Support legacy values of vcs.scm_app_implementation. Legacy
623 623 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
624 624 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
625 625 scm_app_impl = settings['vcs.scm_app_implementation']
626 626 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
627 627 settings['vcs.scm_app_implementation'] = 'http'
628 628
629 629
630 630 def _sanitize_cache_settings(settings):
631 631 temp_store = tempfile.gettempdir()
632 632 default_cache_dir = os.path.join(temp_store, 'rc_cache')
633 633
634 634 # save default, cache dir, and use it for all backends later.
635 635 default_cache_dir = _string_setting(
636 636 settings,
637 637 'cache_dir',
638 638 default_cache_dir, lower=False, default_when_empty=True)
639 639
640 640 # ensure we have our dir created
641 641 if not os.path.isdir(default_cache_dir):
642 642 os.makedirs(default_cache_dir, mode=0o755)
643 643
644 644 # exception store cache
645 645 _string_setting(
646 646 settings,
647 647 'exception_tracker.store_path',
648 648 temp_store, lower=False, default_when_empty=True)
649 649 _bool_setting(
650 650 settings,
651 651 'exception_tracker.send_email',
652 652 'false')
653 653 _string_setting(
654 654 settings,
655 655 'exception_tracker.email_prefix',
656 656 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
657 657
658 658 # cache_perms
659 659 _string_setting(
660 660 settings,
661 661 'rc_cache.cache_perms.backend',
662 662 'dogpile.cache.rc.file_namespace', lower=False)
663 663 _int_setting(
664 664 settings,
665 665 'rc_cache.cache_perms.expiration_time',
666 666 60)
667 667 _string_setting(
668 668 settings,
669 669 'rc_cache.cache_perms.arguments.filename',
670 670 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
671 671
672 672 # cache_repo
673 673 _string_setting(
674 674 settings,
675 675 'rc_cache.cache_repo.backend',
676 676 'dogpile.cache.rc.file_namespace', lower=False)
677 677 _int_setting(
678 678 settings,
679 679 'rc_cache.cache_repo.expiration_time',
680 680 60)
681 681 _string_setting(
682 682 settings,
683 683 'rc_cache.cache_repo.arguments.filename',
684 684 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
685 685
686 686 # cache_license
687 687 _string_setting(
688 688 settings,
689 689 'rc_cache.cache_license.backend',
690 690 'dogpile.cache.rc.file_namespace', lower=False)
691 691 _int_setting(
692 692 settings,
693 693 'rc_cache.cache_license.expiration_time',
694 694 5*60)
695 695 _string_setting(
696 696 settings,
697 697 'rc_cache.cache_license.arguments.filename',
698 698 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
699 699
700 700 # cache_repo_longterm memory, 96H
701 701 _string_setting(
702 702 settings,
703 703 'rc_cache.cache_repo_longterm.backend',
704 704 'dogpile.cache.rc.memory_lru', lower=False)
705 705 _int_setting(
706 706 settings,
707 707 'rc_cache.cache_repo_longterm.expiration_time',
708 708 345600)
709 709 _int_setting(
710 710 settings,
711 711 'rc_cache.cache_repo_longterm.max_size',
712 712 10000)
713 713
714 714 # sql_cache_short
715 715 _string_setting(
716 716 settings,
717 717 'rc_cache.sql_cache_short.backend',
718 718 'dogpile.cache.rc.memory_lru', lower=False)
719 719 _int_setting(
720 720 settings,
721 721 'rc_cache.sql_cache_short.expiration_time',
722 722 30)
723 723 _int_setting(
724 724 settings,
725 725 'rc_cache.sql_cache_short.max_size',
726 726 10000)
727 727
728 728
729 729 def _int_setting(settings, name, default):
730 730 settings[name] = int(settings.get(name, default))
731 731 return settings[name]
732 732
733 733
734 734 def _bool_setting(settings, name, default):
735 735 input_val = settings.get(name, default)
736 736 if isinstance(input_val, unicode):
737 737 input_val = input_val.encode('utf8')
738 738 settings[name] = asbool(input_val)
739 739 return settings[name]
740 740
741 741
742 742 def _list_setting(settings, name, default):
743 743 raw_value = settings.get(name, default)
744 744
745 745 old_separator = ','
746 746 if old_separator in raw_value:
747 747 # If we get a comma separated list, pass it to our own function.
748 748 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
749 749 else:
750 750 # Otherwise we assume it uses pyramids space/newline separation.
751 751 settings[name] = aslist(raw_value)
752 752 return settings[name]
753 753
754 754
755 755 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
756 756 value = settings.get(name, default)
757 757
758 758 if default_when_empty and not value:
759 759 # use default value when value is empty
760 760 value = default
761 761
762 762 if lower:
763 763 value = value.lower()
764 764 settings[name] = value
765 765 return settings[name]
766 766
767 767
768 768 def _substitute_values(mapping, substitutions):
769 769 result = {}
770 770
771 771 try:
772 772 for key, value in mapping.items():
773 773 # initialize without substitution first
774 774 result[key] = value
775 775
776 776 # Note: Cannot use regular replacements, since they would clash
777 777 # with the implementation of ConfigParser. Using "format" instead.
778 778 try:
779 779 result[key] = value.format(**substitutions)
780 780 except KeyError as e:
781 781 env_var = '{}'.format(e.args[0])
782 782
783 783 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
784 784 'Make sure your environment has {var} set, or remove this ' \
785 785 'variable from config file'.format(key=key, var=env_var)
786 786
787 787 if env_var.startswith('ENV_'):
788 788 raise ValueError(msg)
789 789 else:
790 790 log.warning(msg)
791 791
792 792 except ValueError as e:
793 793 log.warning('Failed to substitute ENV variable: %s', e)
794 794 result = mapping
795 795
796 796 return result
@@ -1,526 +1,535 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-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 """
23 23 Set of hooks run by RhodeCode Enterprise
24 24 """
25 25
26 26 import os
27 27 import logging
28 28
29 29 import rhodecode
30 30 from rhodecode import events
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib import audit_logger
33 33 from rhodecode.lib.utils2 import safe_str
34 34 from rhodecode.lib.exceptions import (
35 35 HTTPLockedRC, HTTPBranchProtected, UserCreationError)
36 36 from rhodecode.model.db import Repository, User
37 from rhodecode.lib.statsd_client import StatsdClient
37 38
38 39 log = logging.getLogger(__name__)
39 40
40 41
41 42 class HookResponse(object):
42 43 def __init__(self, status, output):
43 44 self.status = status
44 45 self.output = output
45 46
46 47 def __add__(self, other):
47 48 other_status = getattr(other, 'status', 0)
48 49 new_status = max(self.status, other_status)
49 50 other_output = getattr(other, 'output', '')
50 51 new_output = self.output + other_output
51 52
52 53 return HookResponse(new_status, new_output)
53 54
54 55 def __bool__(self):
55 56 return self.status == 0
56 57
57 58
58 59 def is_shadow_repo(extras):
59 60 """
60 61 Returns ``True`` if this is an action executed against a shadow repository.
61 62 """
62 63 return extras['is_shadow_repo']
63 64
64 65
65 66 def _get_scm_size(alias, root_path):
66 67
67 68 if not alias.startswith('.'):
68 69 alias += '.'
69 70
70 71 size_scm, size_root = 0, 0
71 72 for path, unused_dirs, files in os.walk(safe_str(root_path)):
72 73 if path.find(alias) != -1:
73 74 for f in files:
74 75 try:
75 76 size_scm += os.path.getsize(os.path.join(path, f))
76 77 except OSError:
77 78 pass
78 79 else:
79 80 for f in files:
80 81 try:
81 82 size_root += os.path.getsize(os.path.join(path, f))
82 83 except OSError:
83 84 pass
84 85
85 86 size_scm_f = h.format_byte_size_binary(size_scm)
86 87 size_root_f = h.format_byte_size_binary(size_root)
87 88 size_total_f = h.format_byte_size_binary(size_root + size_scm)
88 89
89 90 return size_scm_f, size_root_f, size_total_f
90 91
91 92
92 93 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
93 94 def repo_size(extras):
94 95 """Present size of repository after push."""
95 96 repo = Repository.get_by_repo_name(extras.repository)
96 97 vcs_part = safe_str(u'.%s' % repo.repo_type)
97 98 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
98 99 repo.repo_full_path)
99 100 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
100 101 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
101 102 return HookResponse(0, msg)
102 103
103 104
104 105 def pre_push(extras):
105 106 """
106 107 Hook executed before pushing code.
107 108
108 109 It bans pushing when the repository is locked.
109 110 """
110 111
111 112 user = User.get_by_username(extras.username)
112 113 output = ''
113 114 if extras.locked_by[0] and user.user_id != int(extras.locked_by[0]):
114 115 locked_by = User.get(extras.locked_by[0]).username
115 116 reason = extras.locked_by[2]
116 117 # this exception is interpreted in git/hg middlewares and based
117 118 # on that proper return code is server to client
118 119 _http_ret = HTTPLockedRC(
119 120 _locked_by_explanation(extras.repository, locked_by, reason))
120 121 if str(_http_ret.code).startswith('2'):
121 122 # 2xx Codes don't raise exceptions
122 123 output = _http_ret.title
123 124 else:
124 125 raise _http_ret
125 126
126 127 hook_response = ''
127 128 if not is_shadow_repo(extras):
128 129 if extras.commit_ids and extras.check_branch_perms:
129 130
130 131 auth_user = user.AuthUser()
131 132 repo = Repository.get_by_repo_name(extras.repository)
132 133 affected_branches = []
133 134 if repo.repo_type == 'hg':
134 135 for entry in extras.commit_ids:
135 136 if entry['type'] == 'branch':
136 137 is_forced = bool(entry['multiple_heads'])
137 138 affected_branches.append([entry['name'], is_forced])
138 139 elif repo.repo_type == 'git':
139 140 for entry in extras.commit_ids:
140 141 if entry['type'] == 'heads':
141 142 is_forced = bool(entry['pruned_sha'])
142 143 affected_branches.append([entry['name'], is_forced])
143 144
144 145 for branch_name, is_forced in affected_branches:
145 146
146 147 rule, branch_perm = auth_user.get_rule_and_branch_permission(
147 148 extras.repository, branch_name)
148 149 if not branch_perm:
149 150 # no branch permission found for this branch, just keep checking
150 151 continue
151 152
152 153 if branch_perm == 'branch.push_force':
153 154 continue
154 155 elif branch_perm == 'branch.push' and is_forced is False:
155 156 continue
156 157 elif branch_perm == 'branch.push' and is_forced is True:
157 158 halt_message = 'Branch `{}` changes rejected by rule {}. ' \
158 159 'FORCE PUSH FORBIDDEN.'.format(branch_name, rule)
159 160 else:
160 161 halt_message = 'Branch `{}` changes rejected by rule {}.'.format(
161 162 branch_name, rule)
162 163
163 164 if halt_message:
164 165 _http_ret = HTTPBranchProtected(halt_message)
165 166 raise _http_ret
166 167
167 168 # Propagate to external components. This is done after checking the
168 169 # lock, for consistent behavior.
169 170 hook_response = pre_push_extension(
170 171 repo_store_path=Repository.base_path(), **extras)
171 172 events.trigger(events.RepoPrePushEvent(
172 173 repo_name=extras.repository, extras=extras))
173 174
174 175 return HookResponse(0, output) + hook_response
175 176
176 177
177 178 def pre_pull(extras):
178 179 """
179 180 Hook executed before pulling the code.
180 181
181 182 It bans pulling when the repository is locked.
182 183 """
183 184
184 185 output = ''
185 186 if extras.locked_by[0]:
186 187 locked_by = User.get(extras.locked_by[0]).username
187 188 reason = extras.locked_by[2]
188 189 # this exception is interpreted in git/hg middlewares and based
189 190 # on that proper return code is server to client
190 191 _http_ret = HTTPLockedRC(
191 192 _locked_by_explanation(extras.repository, locked_by, reason))
192 193 if str(_http_ret.code).startswith('2'):
193 194 # 2xx Codes don't raise exceptions
194 195 output = _http_ret.title
195 196 else:
196 197 raise _http_ret
197 198
198 199 # Propagate to external components. This is done after checking the
199 200 # lock, for consistent behavior.
200 201 hook_response = ''
201 202 if not is_shadow_repo(extras):
202 203 extras.hook_type = extras.hook_type or 'pre_pull'
203 204 hook_response = pre_pull_extension(
204 205 repo_store_path=Repository.base_path(), **extras)
205 206 events.trigger(events.RepoPrePullEvent(
206 207 repo_name=extras.repository, extras=extras))
207 208
208 209 return HookResponse(0, output) + hook_response
209 210
210 211
211 212 def post_pull(extras):
212 213 """Hook executed after client pulls the code."""
213 214
214 215 audit_user = audit_logger.UserWrap(
215 216 username=extras.username,
216 217 ip_addr=extras.ip)
217 218 repo = audit_logger.RepoWrap(repo_name=extras.repository)
218 219 audit_logger.store(
219 220 'user.pull', action_data={'user_agent': extras.user_agent},
220 221 user=audit_user, repo=repo, commit=True)
221 222
223 statsd = StatsdClient.statsd
224 if statsd:
225 statsd.incr('rhodecode_pull')
226
222 227 output = ''
223 228 # make lock is a tri state False, True, None. We only make lock on True
224 229 if extras.make_lock is True and not is_shadow_repo(extras):
225 230 user = User.get_by_username(extras.username)
226 231 Repository.lock(Repository.get_by_repo_name(extras.repository),
227 232 user.user_id,
228 233 lock_reason=Repository.LOCK_PULL)
229 234 msg = 'Made lock on repo `%s`' % (extras.repository,)
230 235 output += msg
231 236
232 237 if extras.locked_by[0]:
233 238 locked_by = User.get(extras.locked_by[0]).username
234 239 reason = extras.locked_by[2]
235 240 _http_ret = HTTPLockedRC(
236 241 _locked_by_explanation(extras.repository, locked_by, reason))
237 242 if str(_http_ret.code).startswith('2'):
238 243 # 2xx Codes don't raise exceptions
239 244 output += _http_ret.title
240 245
241 246 # Propagate to external components.
242 247 hook_response = ''
243 248 if not is_shadow_repo(extras):
244 249 extras.hook_type = extras.hook_type or 'post_pull'
245 250 hook_response = post_pull_extension(
246 251 repo_store_path=Repository.base_path(), **extras)
247 252 events.trigger(events.RepoPullEvent(
248 253 repo_name=extras.repository, extras=extras))
249 254
250 255 return HookResponse(0, output) + hook_response
251 256
252 257
253 258 def post_push(extras):
254 259 """Hook executed after user pushes to the repository."""
255 260 commit_ids = extras.commit_ids
256 261
257 262 # log the push call
258 263 audit_user = audit_logger.UserWrap(
259 264 username=extras.username, ip_addr=extras.ip)
260 265 repo = audit_logger.RepoWrap(repo_name=extras.repository)
261 266 audit_logger.store(
262 267 'user.push', action_data={
263 268 'user_agent': extras.user_agent,
264 269 'commit_ids': commit_ids[:400]},
265 270 user=audit_user, repo=repo, commit=True)
266 271
272 statsd = StatsdClient.statsd
273 if statsd:
274 statsd.incr('rhodecode_push')
275
267 276 # Propagate to external components.
268 277 output = ''
269 278 # make lock is a tri state False, True, None. We only release lock on False
270 279 if extras.make_lock is False and not is_shadow_repo(extras):
271 280 Repository.unlock(Repository.get_by_repo_name(extras.repository))
272 281 msg = 'Released lock on repo `{}`\n'.format(safe_str(extras.repository))
273 282 output += msg
274 283
275 284 if extras.locked_by[0]:
276 285 locked_by = User.get(extras.locked_by[0]).username
277 286 reason = extras.locked_by[2]
278 287 _http_ret = HTTPLockedRC(
279 288 _locked_by_explanation(extras.repository, locked_by, reason))
280 289 # TODO: johbo: if not?
281 290 if str(_http_ret.code).startswith('2'):
282 291 # 2xx Codes don't raise exceptions
283 292 output += _http_ret.title
284 293
285 294 if extras.new_refs:
286 295 tmpl = '{}/{}/pull-request/new?{{ref_type}}={{ref_name}}'.format(
287 296 safe_str(extras.server_url), safe_str(extras.repository))
288 297
289 298 for branch_name in extras.new_refs['branches']:
290 299 output += 'RhodeCode: open pull request link: {}\n'.format(
291 300 tmpl.format(ref_type='branch', ref_name=safe_str(branch_name)))
292 301
293 302 for book_name in extras.new_refs['bookmarks']:
294 303 output += 'RhodeCode: open pull request link: {}\n'.format(
295 304 tmpl.format(ref_type='bookmark', ref_name=safe_str(book_name)))
296 305
297 306 hook_response = ''
298 307 if not is_shadow_repo(extras):
299 308 hook_response = post_push_extension(
300 309 repo_store_path=Repository.base_path(),
301 310 **extras)
302 311 events.trigger(events.RepoPushEvent(
303 312 repo_name=extras.repository, pushed_commit_ids=commit_ids, extras=extras))
304 313
305 314 output += 'RhodeCode: push completed\n'
306 315 return HookResponse(0, output) + hook_response
307 316
308 317
309 318 def _locked_by_explanation(repo_name, user_name, reason):
310 319 message = (
311 320 'Repository `%s` locked by user `%s`. Reason:`%s`'
312 321 % (repo_name, user_name, reason))
313 322 return message
314 323
315 324
316 325 def check_allowed_create_user(user_dict, created_by, **kwargs):
317 326 # pre create hooks
318 327 if pre_create_user.is_active():
319 328 hook_result = pre_create_user(created_by=created_by, **user_dict)
320 329 allowed = hook_result.status == 0
321 330 if not allowed:
322 331 reason = hook_result.output
323 332 raise UserCreationError(reason)
324 333
325 334
326 335 class ExtensionCallback(object):
327 336 """
328 337 Forwards a given call to rcextensions, sanitizes keyword arguments.
329 338
330 339 Does check if there is an extension active for that hook. If it is
331 340 there, it will forward all `kwargs_keys` keyword arguments to the
332 341 extension callback.
333 342 """
334 343
335 344 def __init__(self, hook_name, kwargs_keys):
336 345 self._hook_name = hook_name
337 346 self._kwargs_keys = set(kwargs_keys)
338 347
339 348 def __call__(self, *args, **kwargs):
340 349 log.debug('Calling extension callback for `%s`', self._hook_name)
341 350 callback = self._get_callback()
342 351 if not callback:
343 352 log.debug('extension callback `%s` not found, skipping...', self._hook_name)
344 353 return
345 354
346 355 kwargs_to_pass = {}
347 356 for key in self._kwargs_keys:
348 357 try:
349 358 kwargs_to_pass[key] = kwargs[key]
350 359 except KeyError:
351 360 log.error('Failed to fetch %s key from given kwargs. '
352 361 'Expected keys: %s', key, self._kwargs_keys)
353 362 raise
354 363
355 364 # backward compat for removed api_key for old hooks. This was it works
356 365 # with older rcextensions that require api_key present
357 366 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
358 367 kwargs_to_pass['api_key'] = '_DEPRECATED_'
359 368 return callback(**kwargs_to_pass)
360 369
361 370 def is_active(self):
362 371 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
363 372
364 373 def _get_callback(self):
365 374 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
366 375
367 376
368 377 pre_pull_extension = ExtensionCallback(
369 378 hook_name='PRE_PULL_HOOK',
370 379 kwargs_keys=(
371 380 'server_url', 'config', 'scm', 'username', 'ip', 'action',
372 381 'repository', 'hook_type', 'user_agent', 'repo_store_path',))
373 382
374 383
375 384 post_pull_extension = ExtensionCallback(
376 385 hook_name='PULL_HOOK',
377 386 kwargs_keys=(
378 387 'server_url', 'config', 'scm', 'username', 'ip', 'action',
379 388 'repository', 'hook_type', 'user_agent', 'repo_store_path',))
380 389
381 390
382 391 pre_push_extension = ExtensionCallback(
383 392 hook_name='PRE_PUSH_HOOK',
384 393 kwargs_keys=(
385 394 'server_url', 'config', 'scm', 'username', 'ip', 'action',
386 395 'repository', 'repo_store_path', 'commit_ids', 'hook_type', 'user_agent',))
387 396
388 397
389 398 post_push_extension = ExtensionCallback(
390 399 hook_name='PUSH_HOOK',
391 400 kwargs_keys=(
392 401 'server_url', 'config', 'scm', 'username', 'ip', 'action',
393 402 'repository', 'repo_store_path', 'commit_ids', 'hook_type', 'user_agent',))
394 403
395 404
396 405 pre_create_user = ExtensionCallback(
397 406 hook_name='PRE_CREATE_USER_HOOK',
398 407 kwargs_keys=(
399 408 'username', 'password', 'email', 'firstname', 'lastname', 'active',
400 409 'admin', 'created_by'))
401 410
402 411
403 412 create_pull_request = ExtensionCallback(
404 413 hook_name='CREATE_PULL_REQUEST',
405 414 kwargs_keys=(
406 415 'server_url', 'config', 'scm', 'username', 'ip', 'action',
407 416 'repository', 'pull_request_id', 'url', 'title', 'description',
408 417 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
409 418 'mergeable', 'source', 'target', 'author', 'reviewers'))
410 419
411 420
412 421 merge_pull_request = ExtensionCallback(
413 422 hook_name='MERGE_PULL_REQUEST',
414 423 kwargs_keys=(
415 424 'server_url', 'config', 'scm', 'username', 'ip', 'action',
416 425 'repository', 'pull_request_id', 'url', 'title', 'description',
417 426 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
418 427 'mergeable', 'source', 'target', 'author', 'reviewers'))
419 428
420 429
421 430 close_pull_request = ExtensionCallback(
422 431 hook_name='CLOSE_PULL_REQUEST',
423 432 kwargs_keys=(
424 433 'server_url', 'config', 'scm', 'username', 'ip', 'action',
425 434 'repository', 'pull_request_id', 'url', 'title', 'description',
426 435 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
427 436 'mergeable', 'source', 'target', 'author', 'reviewers'))
428 437
429 438
430 439 review_pull_request = ExtensionCallback(
431 440 hook_name='REVIEW_PULL_REQUEST',
432 441 kwargs_keys=(
433 442 'server_url', 'config', 'scm', 'username', 'ip', 'action',
434 443 'repository', 'pull_request_id', 'url', 'title', 'description',
435 444 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
436 445 'mergeable', 'source', 'target', 'author', 'reviewers'))
437 446
438 447
439 448 comment_pull_request = ExtensionCallback(
440 449 hook_name='COMMENT_PULL_REQUEST',
441 450 kwargs_keys=(
442 451 'server_url', 'config', 'scm', 'username', 'ip', 'action',
443 452 'repository', 'pull_request_id', 'url', 'title', 'description',
444 453 'status', 'comment', 'created_on', 'updated_on', 'commit_ids', 'review_status',
445 454 'mergeable', 'source', 'target', 'author', 'reviewers'))
446 455
447 456
448 457 comment_edit_pull_request = ExtensionCallback(
449 458 hook_name='COMMENT_EDIT_PULL_REQUEST',
450 459 kwargs_keys=(
451 460 'server_url', 'config', 'scm', 'username', 'ip', 'action',
452 461 'repository', 'pull_request_id', 'url', 'title', 'description',
453 462 'status', 'comment', 'created_on', 'updated_on', 'commit_ids', 'review_status',
454 463 'mergeable', 'source', 'target', 'author', 'reviewers'))
455 464
456 465
457 466 update_pull_request = ExtensionCallback(
458 467 hook_name='UPDATE_PULL_REQUEST',
459 468 kwargs_keys=(
460 469 'server_url', 'config', 'scm', 'username', 'ip', 'action',
461 470 'repository', 'pull_request_id', 'url', 'title', 'description',
462 471 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
463 472 'mergeable', 'source', 'target', 'author', 'reviewers'))
464 473
465 474
466 475 create_user = ExtensionCallback(
467 476 hook_name='CREATE_USER_HOOK',
468 477 kwargs_keys=(
469 478 'username', 'full_name_or_username', 'full_contact', 'user_id',
470 479 'name', 'firstname', 'short_contact', 'admin', 'lastname',
471 480 'ip_addresses', 'extern_type', 'extern_name',
472 481 'email', 'api_keys', 'last_login',
473 482 'full_name', 'active', 'password', 'emails',
474 483 'inherit_default_permissions', 'created_by', 'created_on'))
475 484
476 485
477 486 delete_user = ExtensionCallback(
478 487 hook_name='DELETE_USER_HOOK',
479 488 kwargs_keys=(
480 489 'username', 'full_name_or_username', 'full_contact', 'user_id',
481 490 'name', 'firstname', 'short_contact', 'admin', 'lastname',
482 491 'ip_addresses',
483 492 'email', 'last_login',
484 493 'full_name', 'active', 'password', 'emails',
485 494 'inherit_default_permissions', 'deleted_by'))
486 495
487 496
488 497 create_repository = ExtensionCallback(
489 498 hook_name='CREATE_REPO_HOOK',
490 499 kwargs_keys=(
491 500 'repo_name', 'repo_type', 'description', 'private', 'created_on',
492 501 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
493 502 'clone_uri', 'fork_id', 'group_id', 'created_by'))
494 503
495 504
496 505 delete_repository = ExtensionCallback(
497 506 hook_name='DELETE_REPO_HOOK',
498 507 kwargs_keys=(
499 508 'repo_name', 'repo_type', 'description', 'private', 'created_on',
500 509 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
501 510 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
502 511
503 512
504 513 comment_commit_repository = ExtensionCallback(
505 514 hook_name='COMMENT_COMMIT_REPO_HOOK',
506 515 kwargs_keys=(
507 516 'repo_name', 'repo_type', 'description', 'private', 'created_on',
508 517 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
509 518 'clone_uri', 'fork_id', 'group_id',
510 519 'repository', 'created_by', 'comment', 'commit'))
511 520
512 521 comment_edit_commit_repository = ExtensionCallback(
513 522 hook_name='COMMENT_EDIT_COMMIT_REPO_HOOK',
514 523 kwargs_keys=(
515 524 'repo_name', 'repo_type', 'description', 'private', 'created_on',
516 525 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
517 526 'clone_uri', 'fork_id', 'group_id',
518 527 'repository', 'created_by', 'comment', 'commit'))
519 528
520 529
521 530 create_repository_group = ExtensionCallback(
522 531 hook_name='CREATE_REPO_GROUP_HOOK',
523 532 kwargs_keys=(
524 533 'group_name', 'group_parent_id', 'group_description',
525 534 'group_id', 'user_id', 'created_by', 'created_on',
526 535 'enable_locking'))
@@ -1,90 +1,90 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-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 time
22 22 import logging
23 23
24 24 import rhodecode
25 25 from rhodecode.lib.auth import AuthUser
26 26 from rhodecode.lib.base import get_ip_addr, get_access_path, get_user_agent
27 27 from rhodecode.lib.utils2 import safe_str, get_current_rhodecode_user
28 28
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 class RequestWrapperTween(object):
34 34 def __init__(self, handler, registry):
35 35 self.handler = handler
36 36 self.registry = registry
37 37
38 38 # one-time configuration code goes here
39 39
40 40 def _get_user_info(self, request):
41 41 user = get_current_rhodecode_user(request)
42 42 if not user:
43 43 user = AuthUser.repr_user(ip=get_ip_addr(request.environ))
44 44 return user
45 45
46 46 def __call__(self, request):
47 47 start = time.time()
48 48 log.debug('Starting request time measurement')
49 49 try:
50 50 response = self.handler(request)
51 51 finally:
52 52 count = request.request_count()
53 53 _ver_ = rhodecode.__version__
54 54 _path = safe_str(get_access_path(request.environ))
55 55 _auth_user = self._get_user_info(request)
56 56
57 57 total = time.time() - start
58 58 log.info(
59 59 'Req[%4s] %s %s Request to %s time: %.4fs [%s], RhodeCode %s',
60 60 count, _auth_user, request.environ.get('REQUEST_METHOD'),
61 61 _path, total, get_user_agent(request. environ), _ver_
62 62 )
63 63
64 64 statsd = request.registry.statsd
65 65 if statsd:
66 66 resp_code = response.status_code
67 67 user_id = getattr(_auth_user, 'user_id', _auth_user)
68 68 elapsed_time_ms = 1000.0 * total
69 69 statsd.timing(
70 70 'rhodecode_req_timing', elapsed_time_ms,
71 71 tags=[
72 "path:{}".format(_path),
73 "user:{}".format(user_id),
72 #"path:{}".format(_path),
73 #"user:{}".format(user_id),
74 74 "code:{}".format(resp_code)
75 75 ]
76 76 )
77 77 statsd.incr(
78 78 'rhodecode_req_count', tags=[
79 "path:{}".format(_path),
80 "user:{}".format(user_id),
79 #"path:{}".format(_path),
80 #"user:{}".format(user_id),
81 81 "code:{}".format(resp_code)
82 82 ])
83 83
84 84 return response
85 85
86 86
87 87 def includeme(config):
88 88 config.add_tween(
89 89 'rhodecode.lib.middleware.request_wrapper.RequestWrapperTween',
90 90 )
General Comments 0
You need to be logged in to leave comments. Login now