##// END OF EJS Templates
caches: remove settings cache to use invalidation context as it's slowing down much usage
super-admin -
r4833:54305e88 default
parent child Browse files
Show More
@@ -1,614 +1,619 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 collections
24 24 import tempfile
25 25 import time
26 26 import logging.config
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.settings_maker import SettingsMaker
42 42 from rhodecode.config.environment import load_pyramid_environment
43 43
44 44 import rhodecode.events
45 45 from rhodecode.lib.middleware.vcs import VCSMiddleware
46 46 from rhodecode.lib.request import Request
47 47 from rhodecode.lib.vcs import VCSCommunicationError
48 48 from rhodecode.lib.exceptions import VCSServerUnavailable
49 49 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
50 50 from rhodecode.lib.middleware.https_fixup import HttpsFixup
51 51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 52 from rhodecode.lib.utils2 import AttributeDict
53 53 from rhodecode.lib.exc_tracking import store_exception
54 54 from rhodecode.subscribers import (
55 55 scan_repositories_if_enabled, write_js_routes_if_enabled,
56 56 write_metadata_if_needed, write_usage_data)
57 57 from rhodecode.lib.statsd_client import StatsdClient
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 def is_http_error(response):
63 63 # error which should have traceback
64 64 return response.status_code > 499
65 65
66 66
67 67 def should_load_all():
68 68 """
69 69 Returns if all application components should be loaded. In some cases it's
70 70 desired to skip apps loading for faster shell script execution
71 71 """
72 72 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
73 73 if ssh_cmd:
74 74 return False
75 75
76 76 return True
77 77
78 78
79 79 def make_pyramid_app(global_config, **settings):
80 80 """
81 81 Constructs the WSGI application based on Pyramid.
82 82
83 83 Specials:
84 84
85 85 * The application can also be integrated like a plugin via the call to
86 86 `includeme`. This is accompanied with the other utility functions which
87 87 are called. Changing this should be done with great care to not break
88 88 cases when these fragments are assembled from another place.
89 89
90 90 """
91 91 start_time = time.time()
92 92 log.info('Pyramid app config starting')
93 93
94 94 sanitize_settings_and_apply_defaults(global_config, settings)
95 95
96 96 # init and bootstrap StatsdClient
97 97 StatsdClient.setup(settings)
98 98
99 99 config = Configurator(settings=settings)
100 100 # Init our statsd at very start
101 101 config.registry.statsd = StatsdClient.statsd
102 102
103 103 # Apply compatibility patches
104 104 patches.inspect_getargspec()
105 105
106 106 load_pyramid_environment(global_config, settings)
107 107
108 108 # Static file view comes first
109 109 includeme_first(config)
110 110
111 111 includeme(config)
112 112
113 113 pyramid_app = config.make_wsgi_app()
114 114 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
115 115 pyramid_app.config = config
116 116
117 117 celery_settings = get_celery_config(settings)
118 118 config.configure_celery(celery_settings)
119 119
120 120 # creating the app uses a connection - return it after we are done
121 121 meta.Session.remove()
122 122
123 123 total_time = time.time() - start_time
124 124 log.info('Pyramid app `%s` created and configured in %.2fs',
125 125 getattr(pyramid_app, 'func_name', 'pyramid_app'), total_time)
126 126 return pyramid_app
127 127
128 128
129 129 def get_celery_config(settings):
130 130 """
131 131 Converts basic ini configuration into celery 4.X options
132 132 """
133 133
134 134 def key_converter(key_name):
135 135 pref = 'celery.'
136 136 if key_name.startswith(pref):
137 137 return key_name[len(pref):].replace('.', '_').lower()
138 138
139 139 def type_converter(parsed_key, value):
140 140 # cast to int
141 141 if value.isdigit():
142 142 return int(value)
143 143
144 144 # cast to bool
145 145 if value.lower() in ['true', 'false', 'True', 'False']:
146 146 return value.lower() == 'true'
147 147 return value
148 148
149 149 celery_config = {}
150 150 for k, v in settings.items():
151 151 pref = 'celery.'
152 152 if k.startswith(pref):
153 153 celery_config[key_converter(k)] = type_converter(key_converter(k), v)
154 154
155 155 # TODO:rethink if we want to support celerybeat based file config, probably NOT
156 156 # beat_config = {}
157 157 # for section in parser.sections():
158 158 # if section.startswith('celerybeat:'):
159 159 # name = section.split(':', 1)[1]
160 160 # beat_config[name] = get_beat_config(parser, section)
161 161
162 162 # final compose of settings
163 163 celery_settings = {}
164 164
165 165 if celery_config:
166 166 celery_settings.update(celery_config)
167 167 # if beat_config:
168 168 # celery_settings.update({'beat_schedule': beat_config})
169 169
170 170 return celery_settings
171 171
172 172
173 173 def not_found_view(request):
174 174 """
175 175 This creates the view which should be registered as not-found-view to
176 176 pyramid.
177 177 """
178 178
179 179 if not getattr(request, 'vcs_call', None):
180 180 # handle like regular case with our error_handler
181 181 return error_handler(HTTPNotFound(), request)
182 182
183 183 # handle not found view as a vcs call
184 184 settings = request.registry.settings
185 185 ae_client = getattr(request, 'ae_client', None)
186 186 vcs_app = VCSMiddleware(
187 187 HTTPNotFound(), request.registry, settings,
188 188 appenlight_client=ae_client)
189 189
190 190 return wsgiapp(vcs_app)(None, request)
191 191
192 192
193 193 def error_handler(exception, request):
194 194 import rhodecode
195 195 from rhodecode.lib import helpers
196 196 from rhodecode.lib.utils2 import str2bool
197 197
198 198 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
199 199
200 200 base_response = HTTPInternalServerError()
201 201 # prefer original exception for the response since it may have headers set
202 202 if isinstance(exception, HTTPException):
203 203 base_response = exception
204 204 elif isinstance(exception, VCSCommunicationError):
205 205 base_response = VCSServerUnavailable()
206 206
207 207 if is_http_error(base_response):
208 208 log.exception(
209 209 'error occurred handling this request for path: %s', request.path)
210 210
211 211 error_explanation = base_response.explanation or str(base_response)
212 212 if base_response.status_code == 404:
213 213 error_explanation += " Optionally you don't have permission to access this page."
214 214 c = AttributeDict()
215 215 c.error_message = base_response.status
216 216 c.error_explanation = error_explanation
217 217 c.visual = AttributeDict()
218 218
219 219 c.visual.rhodecode_support_url = (
220 220 request.registry.settings.get('rhodecode_support_url') or
221 221 request.route_url('rhodecode_support')
222 222 )
223 223 c.redirect_time = 0
224 224 c.rhodecode_name = rhodecode_title
225 225 if not c.rhodecode_name:
226 226 c.rhodecode_name = 'Rhodecode'
227 227
228 228 c.causes = []
229 229 if is_http_error(base_response):
230 230 c.causes.append('Server is overloaded.')
231 231 c.causes.append('Server database connection is lost.')
232 232 c.causes.append('Server expected unhandled error.')
233 233
234 234 if hasattr(base_response, 'causes'):
235 235 c.causes = base_response.causes
236 236
237 237 c.messages = helpers.flash.pop_messages(request=request)
238 238
239 239 exc_info = sys.exc_info()
240 240 c.exception_id = id(exc_info)
241 241 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
242 242 or base_response.status_code > 499
243 243 c.exception_id_url = request.route_url(
244 244 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
245 245
246 246 if c.show_exception_id:
247 247 store_exception(c.exception_id, exc_info)
248 248 c.exception_debug = str2bool(rhodecode.CONFIG.get('debug'))
249 249 c.exception_config_ini = rhodecode.CONFIG.get('__file__')
250 250
251 251 response = render_to_response(
252 252 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
253 253 response=base_response)
254 254
255 255 statsd = request.registry.statsd
256 256 if statsd and base_response.status_code > 499:
257 257 exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__)
258 258 statsd.incr('rhodecode_exception_total',
259 259 tags=["exc_source:web",
260 260 "http_code:{}".format(base_response.status_code),
261 261 "type:{}".format(exc_type)])
262 262
263 263 return response
264 264
265 265
266 266 def includeme_first(config):
267 267 # redirect automatic browser favicon.ico requests to correct place
268 268 def favicon_redirect(context, request):
269 269 return HTTPFound(
270 270 request.static_path('rhodecode:public/images/favicon.ico'))
271 271
272 272 config.add_view(favicon_redirect, route_name='favicon')
273 273 config.add_route('favicon', '/favicon.ico')
274 274
275 275 def robots_redirect(context, request):
276 276 return HTTPFound(
277 277 request.static_path('rhodecode:public/robots.txt'))
278 278
279 279 config.add_view(robots_redirect, route_name='robots')
280 280 config.add_route('robots', '/robots.txt')
281 281
282 282 config.add_static_view(
283 283 '_static/deform', 'deform:static')
284 284 config.add_static_view(
285 285 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
286 286
287 287
288 288 def includeme(config, auth_resources=None):
289 289 from rhodecode.lib.celerylib.loader import configure_celery
290 290 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
291 291 settings = config.registry.settings
292 292 config.set_request_factory(Request)
293 293
294 294 # plugin information
295 295 config.registry.rhodecode_plugins = collections.OrderedDict()
296 296
297 297 config.add_directive(
298 298 'register_rhodecode_plugin', register_rhodecode_plugin)
299 299
300 300 config.add_directive('configure_celery', configure_celery)
301 301
302 302 if settings.get('appenlight', False):
303 303 config.include('appenlight_client.ext.pyramid_tween')
304 304
305 305 load_all = should_load_all()
306 306
307 307 # Includes which are required. The application would fail without them.
308 308 config.include('pyramid_mako')
309 309 config.include('rhodecode.lib.rc_beaker')
310 310 config.include('rhodecode.lib.rc_cache')
311 311 config.include('rhodecode.apps._base.navigation')
312 312 config.include('rhodecode.apps._base.subscribers')
313 313 config.include('rhodecode.tweens')
314 314 config.include('rhodecode.authentication')
315 315
316 316 if load_all:
317 317 ce_auth_resources = [
318 318 'rhodecode.authentication.plugins.auth_crowd',
319 319 'rhodecode.authentication.plugins.auth_headers',
320 320 'rhodecode.authentication.plugins.auth_jasig_cas',
321 321 'rhodecode.authentication.plugins.auth_ldap',
322 322 'rhodecode.authentication.plugins.auth_pam',
323 323 'rhodecode.authentication.plugins.auth_rhodecode',
324 324 'rhodecode.authentication.plugins.auth_token',
325 325 ]
326 326
327 327 # load CE authentication plugins
328 328
329 329 if auth_resources:
330 330 ce_auth_resources.extend(auth_resources)
331 331
332 332 for resource in ce_auth_resources:
333 333 config.include(resource)
334 334
335 335 # Auto discover authentication plugins and include their configuration.
336 336 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
337 337 from rhodecode.authentication import discover_legacy_plugins
338 338 discover_legacy_plugins(config)
339 339
340 340 # apps
341 341 if load_all:
342 342 config.include('rhodecode.api')
343 343 config.include('rhodecode.apps._base')
344 344 config.include('rhodecode.apps.hovercards')
345 345 config.include('rhodecode.apps.ops')
346 346 config.include('rhodecode.apps.channelstream')
347 347 config.include('rhodecode.apps.file_store')
348 348 config.include('rhodecode.apps.admin')
349 349 config.include('rhodecode.apps.login')
350 350 config.include('rhodecode.apps.home')
351 351 config.include('rhodecode.apps.journal')
352 352
353 353 config.include('rhodecode.apps.repository')
354 354 config.include('rhodecode.apps.repo_group')
355 355 config.include('rhodecode.apps.user_group')
356 356 config.include('rhodecode.apps.search')
357 357 config.include('rhodecode.apps.user_profile')
358 358 config.include('rhodecode.apps.user_group_profile')
359 359 config.include('rhodecode.apps.my_account')
360 360 config.include('rhodecode.apps.gist')
361 361
362 362 config.include('rhodecode.apps.svn_support')
363 363 config.include('rhodecode.apps.ssh_support')
364 364 config.include('rhodecode.apps.debug_style')
365 365
366 366 if load_all:
367 367 config.include('rhodecode.integrations')
368 368
369 369 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
370 370 config.add_translation_dirs('rhodecode:i18n/')
371 371 settings['default_locale_name'] = settings.get('lang', 'en')
372 372
373 373 # Add subscribers.
374 374 if load_all:
375 375 config.add_subscriber(scan_repositories_if_enabled,
376 376 pyramid.events.ApplicationCreated)
377 377 config.add_subscriber(write_metadata_if_needed,
378 378 pyramid.events.ApplicationCreated)
379 379 config.add_subscriber(write_usage_data,
380 380 pyramid.events.ApplicationCreated)
381 381 config.add_subscriber(write_js_routes_if_enabled,
382 382 pyramid.events.ApplicationCreated)
383 383
384 384 # request custom methods
385 385 config.add_request_method(
386 386 'rhodecode.lib.partial_renderer.get_partial_renderer',
387 387 'get_partial_renderer')
388 388
389 389 config.add_request_method(
390 390 'rhodecode.lib.request_counter.get_request_counter',
391 391 'request_count')
392 392
393 393 # Set the authorization policy.
394 394 authz_policy = ACLAuthorizationPolicy()
395 395 config.set_authorization_policy(authz_policy)
396 396
397 397 # Set the default renderer for HTML templates to mako.
398 398 config.add_mako_renderer('.html')
399 399
400 400 config.add_renderer(
401 401 name='json_ext',
402 402 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
403 403
404 404 config.add_renderer(
405 405 name='string_html',
406 406 factory='rhodecode.lib.string_renderer.html')
407 407
408 408 # include RhodeCode plugins
409 409 includes = aslist(settings.get('rhodecode.includes', []))
410 410 for inc in includes:
411 411 config.include(inc)
412 412
413 413 # custom not found view, if our pyramid app doesn't know how to handle
414 414 # the request pass it to potential VCS handling ap
415 415 config.add_notfound_view(not_found_view)
416 416 if not settings.get('debugtoolbar.enabled', False):
417 417 # disabled debugtoolbar handle all exceptions via the error_handlers
418 418 config.add_view(error_handler, context=Exception)
419 419
420 420 # all errors including 403/404/50X
421 421 config.add_view(error_handler, context=HTTPError)
422 422
423 423
424 424 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
425 425 """
426 426 Apply outer WSGI middlewares around the application.
427 427 """
428 428 registry = config.registry
429 429 settings = registry.settings
430 430
431 431 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
432 432 pyramid_app = HttpsFixup(pyramid_app, settings)
433 433
434 434 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
435 435 pyramid_app, settings)
436 436 registry.ae_client = _ae_client
437 437
438 438 if settings['gzip_responses']:
439 439 pyramid_app = make_gzip_middleware(
440 440 pyramid_app, settings, compress_level=1)
441 441
442 442 # this should be the outer most middleware in the wsgi stack since
443 443 # middleware like Routes make database calls
444 444 def pyramid_app_with_cleanup(environ, start_response):
445 445 try:
446 446 return pyramid_app(environ, start_response)
447 447 finally:
448 448 # Dispose current database session and rollback uncommitted
449 449 # transactions.
450 450 meta.Session.remove()
451 451
452 452 # In a single threaded mode server, on non sqlite db we should have
453 453 # '0 Current Checked out connections' at the end of a request,
454 454 # if not, then something, somewhere is leaving a connection open
455 455 pool = meta.Base.metadata.bind.engine.pool
456 456 log.debug('sa pool status: %s', pool.status())
457 457 log.debug('Request processing finalized')
458 458
459 459 return pyramid_app_with_cleanup
460 460
461 461
462 462 def sanitize_settings_and_apply_defaults(global_config, settings):
463 463 """
464 464 Applies settings defaults and does all type conversion.
465 465
466 466 We would move all settings parsing and preparation into this place, so that
467 467 we have only one place left which deals with this part. The remaining parts
468 468 of the application would start to rely fully on well prepared settings.
469 469
470 470 This piece would later be split up per topic to avoid a big fat monster
471 471 function.
472 472 """
473 473
474 474 global_settings_maker = SettingsMaker(global_config)
475 475 global_settings_maker.make_setting('debug', default=False, parser='bool')
476 476 debug_enabled = asbool(global_config.get('debug'))
477 477
478 478 settings_maker = SettingsMaker(settings)
479 479
480 480 settings_maker.make_setting(
481 481 'logging.autoconfigure',
482 482 default=True,
483 483 parser='bool')
484 484
485 485 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
486 486 settings_maker.enable_logging(logging_conf, level='INFO' if debug_enabled else 'DEBUG')
487 487
488 488 # Default includes, possible to change as a user
489 489 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
490 490 log.debug(
491 491 "Using the following pyramid.includes: %s",
492 492 pyramid_includes)
493 493
494 494 settings_maker.make_setting('rhodecode.edition', 'Community Edition')
495 495 settings_maker.make_setting('rhodecode.edition_id', 'CE')
496 496
497 497 if 'mako.default_filters' not in settings:
498 498 # set custom default filters if we don't have it defined
499 499 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
500 500 settings['mako.default_filters'] = 'h_filter'
501 501
502 502 if 'mako.directories' not in settings:
503 503 mako_directories = settings.setdefault('mako.directories', [
504 504 # Base templates of the original application
505 505 'rhodecode:templates',
506 506 ])
507 507 log.debug(
508 508 "Using the following Mako template directories: %s",
509 509 mako_directories)
510 510
511 511 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
512 512 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
513 513 raw_url = settings['beaker.session.url']
514 514 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
515 515 settings['beaker.session.url'] = 'redis://' + raw_url
516 516
517 517 settings_maker.make_setting('__file__', global_config.get('__file__'))
518 518
519 519 # TODO: johbo: Re-think this, usually the call to config.include
520 520 # should allow to pass in a prefix.
521 521 settings_maker.make_setting('rhodecode.api.url', '/_admin/api')
522 522
523 523 # Sanitize generic settings.
524 524 settings_maker.make_setting('default_encoding', 'UTF-8', parser='list')
525 525 settings_maker.make_setting('is_test', False, parser='bool')
526 526 settings_maker.make_setting('gzip_responses', False, parser='bool')
527 527
528 528 # statsd
529 529 settings_maker.make_setting('statsd.enabled', False, parser='bool')
530 530 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
531 531 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
532 532 settings_maker.make_setting('statsd.statsd_prefix', '')
533 533 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
534 534
535 535 settings_maker.make_setting('vcs.svn.compatible_version', '')
536 536 settings_maker.make_setting('vcs.hooks.protocol', 'http')
537 537 settings_maker.make_setting('vcs.hooks.host', '127.0.0.1')
538 538 settings_maker.make_setting('vcs.scm_app_implementation', 'http')
539 539 settings_maker.make_setting('vcs.server', '')
540 540 settings_maker.make_setting('vcs.server.protocol', 'http')
541 541 settings_maker.make_setting('startup.import_repos', 'false', parser='bool')
542 542 settings_maker.make_setting('vcs.hooks.direct_calls', 'false', parser='bool')
543 543 settings_maker.make_setting('vcs.server.enable', 'true', parser='bool')
544 544 settings_maker.make_setting('vcs.start_server', 'false', parser='bool')
545 545 settings_maker.make_setting('vcs.backends', 'hg, git, svn', parser='list')
546 546 settings_maker.make_setting('vcs.connection_timeout', 3600, parser='int')
547 547
548 548 settings_maker.make_setting('vcs.methods.cache', True, parser='bool')
549 549
550 550 # Support legacy values of vcs.scm_app_implementation. Legacy
551 551 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
552 552 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
553 553 scm_app_impl = settings['vcs.scm_app_implementation']
554 554 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
555 555 settings['vcs.scm_app_implementation'] = 'http'
556 556
557 557 settings_maker.make_setting('appenlight', False, parser='bool')
558 558
559 559 temp_store = tempfile.gettempdir()
560 560 default_cache_dir = os.path.join(temp_store, 'rc_cache')
561 561
562 562 # save default, cache dir, and use it for all backends later.
563 563 default_cache_dir = settings_maker.make_setting(
564 564 'cache_dir',
565 565 default=default_cache_dir, default_when_empty=True,
566 566 parser='dir:ensured')
567 567
568 568 # exception store cache
569 569 settings_maker.make_setting(
570 570 'exception_tracker.store_path',
571 571 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
572 572 parser='dir:ensured'
573 573 )
574 574
575 575 settings_maker.make_setting(
576 576 'celerybeat-schedule.path',
577 577 default=os.path.join(default_cache_dir, 'celerybeat_schedule', 'celerybeat-schedule.db'), default_when_empty=True,
578 578 parser='file:ensured'
579 579 )
580 580
581 581 settings_maker.make_setting('exception_tracker.send_email', False, parser='bool')
582 582 settings_maker.make_setting('exception_tracker.email_prefix', '[RHODECODE ERROR]', default_when_empty=True)
583 583
584 # cache_general
585 settings_maker.make_setting('rc_cache.cache_general.backend', 'dogpile.cache.rc.file_namespace')
586 settings_maker.make_setting('rc_cache.cache_general.expiration_time', 60 * 60 * 12, parser='int')
587 settings_maker.make_setting('rc_cache.cache_general.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_general.db'))
588
584 589 # cache_perms
585 590 settings_maker.make_setting('rc_cache.cache_perms.backend', 'dogpile.cache.rc.file_namespace')
586 settings_maker.make_setting('rc_cache.cache_perms.expiration_time', 60, parser='int')
591 settings_maker.make_setting('rc_cache.cache_perms.expiration_time', 60 * 60, parser='int')
587 592 settings_maker.make_setting('rc_cache.cache_perms.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_perms.db'))
588 593
589 594 # cache_repo
590 595 settings_maker.make_setting('rc_cache.cache_repo.backend', 'dogpile.cache.rc.file_namespace')
591 settings_maker.make_setting('rc_cache.cache_repo.expiration_time', 60, parser='int')
596 settings_maker.make_setting('rc_cache.cache_repo.expiration_time', 60 * 60 * 24 * 30, parser='int')
592 597 settings_maker.make_setting('rc_cache.cache_repo.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_repo.db'))
593 598
594 599 # cache_license
595 600 settings_maker.make_setting('rc_cache.cache_license.backend', 'dogpile.cache.rc.file_namespace')
596 settings_maker.make_setting('rc_cache.cache_license.expiration_time', 5*60, parser='int')
601 settings_maker.make_setting('rc_cache.cache_license.expiration_time', 60 * 5, parser='int')
597 602 settings_maker.make_setting('rc_cache.cache_license.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_license.db'))
598 603
599 604 # cache_repo_longterm memory, 96H
600 605 settings_maker.make_setting('rc_cache.cache_repo_longterm.backend', 'dogpile.cache.rc.memory_lru')
601 606 settings_maker.make_setting('rc_cache.cache_repo_longterm.expiration_time', 345600, parser='int')
602 607 settings_maker.make_setting('rc_cache.cache_repo_longterm.max_size', 10000, parser='int')
603 608
604 609 # sql_cache_short
605 610 settings_maker.make_setting('rc_cache.sql_cache_short.backend', 'dogpile.cache.rc.memory_lru')
606 611 settings_maker.make_setting('rc_cache.sql_cache_short.expiration_time', 30, parser='int')
607 612 settings_maker.make_setting('rc_cache.sql_cache_short.max_size', 10000, parser='int')
608 613
609 614 settings_maker.env_expand()
610 615
611 616 # configure instance id
612 617 config_utils.set_instance_id(settings)
613 618
614 619 return settings
@@ -1,920 +1,918 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 hashlib
23 23 import logging
24 24 import re
25 25 from collections import namedtuple
26 26 from functools import wraps
27 27 import bleach
28 28 from pyramid.threadlocal import get_current_request, get_current_registry
29 29
30 30 from rhodecode.lib import rc_cache
31 31 from rhodecode.lib.utils2 import (
32 32 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
33 33 from rhodecode.lib.vcs.backends import base
34 from rhodecode.lib.statsd_client import StatsdClient
34 35 from rhodecode.model import BaseModel
35 36 from rhodecode.model.db import (
36 37 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting, CacheKey)
37 38 from rhodecode.model.meta import Session
38 39
39 40
40 41 log = logging.getLogger(__name__)
41 42
42 43
43 44 UiSetting = namedtuple(
44 45 'UiSetting', ['section', 'key', 'value', 'active'])
45 46
46 47 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
47 48
48 49
49 50 class SettingNotFound(Exception):
50 51 def __init__(self, setting_id):
51 52 msg = 'Setting `{}` is not found'.format(setting_id)
52 53 super(SettingNotFound, self).__init__(msg)
53 54
54 55
55 56 class SettingsModel(BaseModel):
56 57 BUILTIN_HOOKS = (
57 58 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
58 59 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
59 60 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
60 61 RhodeCodeUi.HOOK_PUSH_KEY,)
61 62 HOOKS_SECTION = 'hooks'
62 63
63 64 def __init__(self, sa=None, repo=None):
64 65 self.repo = repo
65 66 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
66 67 self.SettingsDbModel = (
67 68 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
68 69 super(SettingsModel, self).__init__(sa)
69 70
70 71 def get_ui_by_key(self, key):
71 72 q = self.UiDbModel.query()
72 73 q = q.filter(self.UiDbModel.ui_key == key)
73 74 q = self._filter_by_repo(RepoRhodeCodeUi, q)
74 75 return q.scalar()
75 76
76 77 def get_ui_by_section(self, section):
77 78 q = self.UiDbModel.query()
78 79 q = q.filter(self.UiDbModel.ui_section == section)
79 80 q = self._filter_by_repo(RepoRhodeCodeUi, q)
80 81 return q.all()
81 82
82 83 def get_ui_by_section_and_key(self, section, key):
83 84 q = self.UiDbModel.query()
84 85 q = q.filter(self.UiDbModel.ui_section == section)
85 86 q = q.filter(self.UiDbModel.ui_key == key)
86 87 q = self._filter_by_repo(RepoRhodeCodeUi, q)
87 88 return q.scalar()
88 89
89 90 def get_ui(self, section=None, key=None):
90 91 q = self.UiDbModel.query()
91 92 q = self._filter_by_repo(RepoRhodeCodeUi, q)
92 93
93 94 if section:
94 95 q = q.filter(self.UiDbModel.ui_section == section)
95 96 if key:
96 97 q = q.filter(self.UiDbModel.ui_key == key)
97 98
98 99 # TODO: mikhail: add caching
99 100 result = [
100 101 UiSetting(
101 102 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
102 103 value=safe_str(r.ui_value), active=r.ui_active
103 104 )
104 105 for r in q.all()
105 106 ]
106 107 return result
107 108
108 109 def get_builtin_hooks(self):
109 110 q = self.UiDbModel.query()
110 111 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
111 112 return self._get_hooks(q)
112 113
113 114 def get_custom_hooks(self):
114 115 q = self.UiDbModel.query()
115 116 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
116 117 return self._get_hooks(q)
117 118
118 119 def create_ui_section_value(self, section, val, key=None, active=True):
119 120 new_ui = self.UiDbModel()
120 121 new_ui.ui_section = section
121 122 new_ui.ui_value = val
122 123 new_ui.ui_active = active
123 124
124 125 repository_id = ''
125 126 if self.repo:
126 127 repo = self._get_repo(self.repo)
127 128 repository_id = repo.repo_id
128 129 new_ui.repository_id = repository_id
129 130
130 131 if not key:
131 132 # keys are unique so they need appended info
132 133 if self.repo:
133 134 key = hashlib.sha1(
134 135 '{}{}{}'.format(section, val, repository_id)).hexdigest()
135 136 else:
136 137 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
137 138
138 139 new_ui.ui_key = key
139 140
140 141 Session().add(new_ui)
141 142 return new_ui
142 143
143 144 def create_or_update_hook(self, key, value):
144 145 ui = (
145 146 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
146 147 self.UiDbModel())
147 148 ui.ui_section = self.HOOKS_SECTION
148 149 ui.ui_active = True
149 150 ui.ui_key = key
150 151 ui.ui_value = value
151 152
152 153 if self.repo:
153 154 repo = self._get_repo(self.repo)
154 155 repository_id = repo.repo_id
155 156 ui.repository_id = repository_id
156 157
157 158 Session().add(ui)
158 159 return ui
159 160
160 161 def delete_ui(self, id_):
161 162 ui = self.UiDbModel.get(id_)
162 163 if not ui:
163 164 raise SettingNotFound(id_)
164 165 Session().delete(ui)
165 166
166 167 def get_setting_by_name(self, name):
167 168 q = self._get_settings_query()
168 169 q = q.filter(self.SettingsDbModel.app_settings_name == name)
169 170 return q.scalar()
170 171
171 172 def create_or_update_setting(
172 173 self, name, val=Optional(''), type_=Optional('unicode')):
173 174 """
174 175 Creates or updates RhodeCode setting. If updates is triggered it will
175 176 only update parameters that are explicityl set Optional instance will
176 177 be skipped
177 178
178 179 :param name:
179 180 :param val:
180 181 :param type_:
181 182 :return:
182 183 """
183 184
184 185 res = self.get_setting_by_name(name)
185 186 repo = self._get_repo(self.repo) if self.repo else None
186 187
187 188 if not res:
188 189 val = Optional.extract(val)
189 190 type_ = Optional.extract(type_)
190 191
191 192 args = (
192 193 (repo.repo_id, name, val, type_)
193 194 if repo else (name, val, type_))
194 195 res = self.SettingsDbModel(*args)
195 196
196 197 else:
197 198 if self.repo:
198 199 res.repository_id = repo.repo_id
199 200
200 201 res.app_settings_name = name
201 202 if not isinstance(type_, Optional):
202 203 # update if set
203 204 res.app_settings_type = type_
204 205 if not isinstance(val, Optional):
205 206 # update if set
206 207 res.app_settings_value = val
207 208
208 209 Session().add(res)
209 210 return res
210 211
212 def get_cache_region(self):
213 repo = self._get_repo(self.repo) if self.repo else None
214 cache_key = "repo.{}".format(repo.repo_id) if repo else "general_settings"
215 cache_namespace_uid = 'cache_settings.{}'.format(cache_key)
216 region = rc_cache.get_or_create_region('cache_general', cache_namespace_uid)
217 return region, cache_key
218
211 219 def invalidate_settings_cache(self):
212 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
213 CacheKey.set_invalidate(invalidation_namespace)
220 from rhodecode.authentication.base import get_authn_registry
221
222 region, cache_key = self.get_cache_region()
223 log.debug('Invalidation cache region %s for cache_key: %s', region, cache_key)
224 region.invalidate()
225 registry = get_current_registry()
226 if registry:
227 authn_registry = get_authn_registry(registry)
228 if authn_registry:
229 authn_registry.invalidate_plugins_for_auth()
214 230
215 231 def get_all_settings(self, cache=False, from_request=True):
216 from rhodecode.authentication.base import get_authn_registry
217
218 232 # defines if we use GLOBAL, or PER_REPO
219 233 repo = self._get_repo(self.repo) if self.repo else None
220 key = "settings_repo.{}".format(repo.repo_id) if repo else "settings_app"
221 234
222 235 # initially try the requests context, this is the fastest
223 236 # we only fetch global config
224 237 if from_request:
225 238 request = get_current_request()
226 239
227 240 if request and not repo and hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
228 241 rc_config = request.call_context.rc_config
229 242 if rc_config:
230 243 return rc_config
231 244
232 region = rc_cache.get_or_create_region('sql_cache_short')
233 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
245 region, cache_key = self.get_cache_region()
234 246
235 247 @region.conditional_cache_on_arguments(condition=cache)
236 248 def _get_all_settings(name, key):
237 249 q = self._get_settings_query()
238 250 if not q:
239 251 raise Exception('Could not get application settings !')
240 252
241 253 settings = {
242 'rhodecode_' + result.app_settings_name: result.app_settings_value
243 for result in q
254 'rhodecode_' + res.app_settings_name: res.app_settings_value
255 for res in q
244 256 }
245 257 return settings
246 258
247 inv_context_manager = rc_cache.InvalidationContext(
248 uid='cache_settings', invalidation_namespace=invalidation_namespace)
249 with inv_context_manager as invalidation_context:
250 # check for stored invalidation signal, and maybe purge the cache
251 # before computing it again
252 if invalidation_context.should_invalidate():
253 # NOTE:(marcink) we flush the whole sql_cache_short region, because it
254 # reads different settings etc. It's little too much but those caches
255 # are anyway very short lived and it's a safest way.
256 region = rc_cache.get_or_create_region('sql_cache_short')
257 region.invalidate()
258 registry = get_current_registry()
259 if registry:
260 authn_registry = get_authn_registry(registry)
261 if authn_registry:
262 authn_registry.invalidate_plugins_for_auth()
259 statsd = StatsdClient.statsd
260 with statsd.timer('rhodecode_settings_timing.histogram', auto_send=True) as tmr:
261 result = _get_all_settings('rhodecode_settings', cache_key)
263 262
264 result = _get_all_settings('rhodecode_settings', key)
265 log.debug('Fetching app settings for key: %s took: %.4fs', key,
266 inv_context_manager.compute_time)
263 compute_time = tmr.ms / 1000.
264 log.debug('Fetching app settings for key: %s took: %.4fs', cache_key, compute_time)
267 265
268 266 return result
269 267
270 268 def get_auth_settings(self):
271 269 q = self._get_settings_query()
272 270 q = q.filter(
273 271 self.SettingsDbModel.app_settings_name.startswith('auth_'))
274 272 rows = q.all()
275 273 auth_settings = {
276 274 row.app_settings_name: row.app_settings_value for row in rows}
277 275 return auth_settings
278 276
279 277 def get_auth_plugins(self):
280 278 auth_plugins = self.get_setting_by_name("auth_plugins")
281 279 return auth_plugins.app_settings_value
282 280
283 281 def get_default_repo_settings(self, strip_prefix=False):
284 282 q = self._get_settings_query()
285 283 q = q.filter(
286 284 self.SettingsDbModel.app_settings_name.startswith('default_'))
287 285 rows = q.all()
288 286
289 287 result = {}
290 288 for row in rows:
291 289 key = row.app_settings_name
292 290 if strip_prefix:
293 291 key = remove_prefix(key, prefix='default_')
294 292 result.update({key: row.app_settings_value})
295 293 return result
296 294
297 295 def get_repo(self):
298 296 repo = self._get_repo(self.repo)
299 297 if not repo:
300 298 raise Exception(
301 299 'Repository `{}` cannot be found inside the database'.format(
302 300 self.repo))
303 301 return repo
304 302
305 303 def _filter_by_repo(self, model, query):
306 304 if self.repo:
307 305 repo = self.get_repo()
308 306 query = query.filter(model.repository_id == repo.repo_id)
309 307 return query
310 308
311 309 def _get_hooks(self, query):
312 310 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
313 311 query = self._filter_by_repo(RepoRhodeCodeUi, query)
314 312 return query.all()
315 313
316 314 def _get_settings_query(self):
317 315 q = self.SettingsDbModel.query()
318 316 return self._filter_by_repo(RepoRhodeCodeSetting, q)
319 317
320 318 def list_enabled_social_plugins(self, settings):
321 319 enabled = []
322 320 for plug in SOCIAL_PLUGINS_LIST:
323 321 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
324 322 )):
325 323 enabled.append(plug)
326 324 return enabled
327 325
328 326
329 327 def assert_repo_settings(func):
330 328 @wraps(func)
331 329 def _wrapper(self, *args, **kwargs):
332 330 if not self.repo_settings:
333 331 raise Exception('Repository is not specified')
334 332 return func(self, *args, **kwargs)
335 333 return _wrapper
336 334
337 335
338 336 class IssueTrackerSettingsModel(object):
339 337 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
340 338 SETTINGS_PREFIX = 'issuetracker_'
341 339
342 340 def __init__(self, sa=None, repo=None):
343 341 self.global_settings = SettingsModel(sa=sa)
344 342 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
345 343
346 344 @property
347 345 def inherit_global_settings(self):
348 346 if not self.repo_settings:
349 347 return True
350 348 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
351 349 return setting.app_settings_value if setting else True
352 350
353 351 @inherit_global_settings.setter
354 352 def inherit_global_settings(self, value):
355 353 if self.repo_settings:
356 354 settings = self.repo_settings.create_or_update_setting(
357 355 self.INHERIT_SETTINGS, value, type_='bool')
358 356 Session().add(settings)
359 357
360 358 def _get_keyname(self, key, uid, prefix=''):
361 359 return '{0}{1}{2}_{3}'.format(
362 360 prefix, self.SETTINGS_PREFIX, key, uid)
363 361
364 362 def _make_dict_for_settings(self, qs):
365 363 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
366 364
367 365 issuetracker_entries = {}
368 366 # create keys
369 367 for k, v in qs.items():
370 368 if k.startswith(prefix_match):
371 369 uid = k[len(prefix_match):]
372 370 issuetracker_entries[uid] = None
373 371
374 372 def url_cleaner(input_str):
375 373 input_str = input_str.replace('"', '').replace("'", '')
376 374 input_str = bleach.clean(input_str, strip=True)
377 375 return input_str
378 376
379 377 # populate
380 378 for uid in issuetracker_entries:
381 379 url_data = qs.get(self._get_keyname('url', uid, 'rhodecode_'))
382 380
383 381 pat = qs.get(self._get_keyname('pat', uid, 'rhodecode_'))
384 382 try:
385 383 pat_compiled = re.compile(r'%s' % pat)
386 384 except re.error:
387 385 pat_compiled = None
388 386
389 387 issuetracker_entries[uid] = AttributeDict({
390 388 'pat': pat,
391 389 'pat_compiled': pat_compiled,
392 390 'url': url_cleaner(
393 391 qs.get(self._get_keyname('url', uid, 'rhodecode_')) or ''),
394 392 'pref': bleach.clean(
395 393 qs.get(self._get_keyname('pref', uid, 'rhodecode_')) or ''),
396 394 'desc': qs.get(
397 395 self._get_keyname('desc', uid, 'rhodecode_')),
398 396 })
399 397
400 398 return issuetracker_entries
401 399
402 400 def get_global_settings(self, cache=False):
403 401 """
404 402 Returns list of global issue tracker settings
405 403 """
406 404 defaults = self.global_settings.get_all_settings(cache=cache)
407 405 settings = self._make_dict_for_settings(defaults)
408 406 return settings
409 407
410 408 def get_repo_settings(self, cache=False):
411 409 """
412 410 Returns list of issue tracker settings per repository
413 411 """
414 412 if not self.repo_settings:
415 413 raise Exception('Repository is not specified')
416 414 all_settings = self.repo_settings.get_all_settings(cache=cache)
417 415 settings = self._make_dict_for_settings(all_settings)
418 416 return settings
419 417
420 418 def get_settings(self, cache=False):
421 419 if self.inherit_global_settings:
422 420 return self.get_global_settings(cache=cache)
423 421 else:
424 422 return self.get_repo_settings(cache=cache)
425 423
426 424 def delete_entries(self, uid):
427 425 if self.repo_settings:
428 426 all_patterns = self.get_repo_settings()
429 427 settings_model = self.repo_settings
430 428 else:
431 429 all_patterns = self.get_global_settings()
432 430 settings_model = self.global_settings
433 431 entries = all_patterns.get(uid, [])
434 432
435 433 for del_key in entries:
436 434 setting_name = self._get_keyname(del_key, uid)
437 435 entry = settings_model.get_setting_by_name(setting_name)
438 436 if entry:
439 437 Session().delete(entry)
440 438
441 439 Session().commit()
442 440
443 441 def create_or_update_setting(
444 442 self, name, val=Optional(''), type_=Optional('unicode')):
445 443 if self.repo_settings:
446 444 setting = self.repo_settings.create_or_update_setting(
447 445 name, val, type_)
448 446 else:
449 447 setting = self.global_settings.create_or_update_setting(
450 448 name, val, type_)
451 449 return setting
452 450
453 451
454 452 class VcsSettingsModel(object):
455 453
456 454 INHERIT_SETTINGS = 'inherit_vcs_settings'
457 455 GENERAL_SETTINGS = (
458 456 'use_outdated_comments',
459 457 'pr_merge_enabled',
460 458 'hg_use_rebase_for_merging',
461 459 'hg_close_branch_before_merging',
462 460 'git_use_rebase_for_merging',
463 461 'git_close_branch_before_merging',
464 462 'diff_cache',
465 463 )
466 464
467 465 HOOKS_SETTINGS = (
468 466 ('hooks', 'changegroup.repo_size'),
469 467 ('hooks', 'changegroup.push_logger'),
470 468 ('hooks', 'outgoing.pull_logger'),
471 469 )
472 470 HG_SETTINGS = (
473 471 ('extensions', 'largefiles'),
474 472 ('phases', 'publish'),
475 473 ('extensions', 'evolve'),
476 474 ('extensions', 'topic'),
477 475 ('experimental', 'evolution'),
478 476 ('experimental', 'evolution.exchange'),
479 477 )
480 478 GIT_SETTINGS = (
481 479 ('vcs_git_lfs', 'enabled'),
482 480 )
483 481 GLOBAL_HG_SETTINGS = (
484 482 ('extensions', 'largefiles'),
485 483 ('largefiles', 'usercache'),
486 484 ('phases', 'publish'),
487 485 ('extensions', 'hgsubversion'),
488 486 ('extensions', 'evolve'),
489 487 ('extensions', 'topic'),
490 488 ('experimental', 'evolution'),
491 489 ('experimental', 'evolution.exchange'),
492 490 )
493 491
494 492 GLOBAL_GIT_SETTINGS = (
495 493 ('vcs_git_lfs', 'enabled'),
496 494 ('vcs_git_lfs', 'store_location')
497 495 )
498 496
499 497 GLOBAL_SVN_SETTINGS = (
500 498 ('vcs_svn_proxy', 'http_requests_enabled'),
501 499 ('vcs_svn_proxy', 'http_server_url')
502 500 )
503 501
504 502 SVN_BRANCH_SECTION = 'vcs_svn_branch'
505 503 SVN_TAG_SECTION = 'vcs_svn_tag'
506 504 SSL_SETTING = ('web', 'push_ssl')
507 505 PATH_SETTING = ('paths', '/')
508 506
509 507 def __init__(self, sa=None, repo=None):
510 508 self.global_settings = SettingsModel(sa=sa)
511 509 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
512 510 self._ui_settings = (
513 511 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
514 512 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
515 513
516 514 @property
517 515 @assert_repo_settings
518 516 def inherit_global_settings(self):
519 517 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
520 518 return setting.app_settings_value if setting else True
521 519
522 520 @inherit_global_settings.setter
523 521 @assert_repo_settings
524 522 def inherit_global_settings(self, value):
525 523 self.repo_settings.create_or_update_setting(
526 524 self.INHERIT_SETTINGS, value, type_='bool')
527 525
528 526 def get_global_svn_branch_patterns(self):
529 527 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
530 528
531 529 @assert_repo_settings
532 530 def get_repo_svn_branch_patterns(self):
533 531 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
534 532
535 533 def get_global_svn_tag_patterns(self):
536 534 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
537 535
538 536 @assert_repo_settings
539 537 def get_repo_svn_tag_patterns(self):
540 538 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
541 539
542 540 def get_global_settings(self):
543 541 return self._collect_all_settings(global_=True)
544 542
545 543 @assert_repo_settings
546 544 def get_repo_settings(self):
547 545 return self._collect_all_settings(global_=False)
548 546
549 547 @assert_repo_settings
550 548 def get_repo_settings_inherited(self):
551 549 global_settings = self.get_global_settings()
552 550 global_settings.update(self.get_repo_settings())
553 551 return global_settings
554 552
555 553 @assert_repo_settings
556 554 def create_or_update_repo_settings(
557 555 self, data, inherit_global_settings=False):
558 556 from rhodecode.model.scm import ScmModel
559 557
560 558 self.inherit_global_settings = inherit_global_settings
561 559
562 560 repo = self.repo_settings.get_repo()
563 561 if not inherit_global_settings:
564 562 if repo.repo_type == 'svn':
565 563 self.create_repo_svn_settings(data)
566 564 else:
567 565 self.create_or_update_repo_hook_settings(data)
568 566 self.create_or_update_repo_pr_settings(data)
569 567
570 568 if repo.repo_type == 'hg':
571 569 self.create_or_update_repo_hg_settings(data)
572 570
573 571 if repo.repo_type == 'git':
574 572 self.create_or_update_repo_git_settings(data)
575 573
576 574 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
577 575
578 576 @assert_repo_settings
579 577 def create_or_update_repo_hook_settings(self, data):
580 578 for section, key in self.HOOKS_SETTINGS:
581 579 data_key = self._get_form_ui_key(section, key)
582 580 if data_key not in data:
583 581 raise ValueError(
584 582 'The given data does not contain {} key'.format(data_key))
585 583
586 584 active = data.get(data_key)
587 585 repo_setting = self.repo_settings.get_ui_by_section_and_key(
588 586 section, key)
589 587 if not repo_setting:
590 588 global_setting = self.global_settings.\
591 589 get_ui_by_section_and_key(section, key)
592 590 self.repo_settings.create_ui_section_value(
593 591 section, global_setting.ui_value, key=key, active=active)
594 592 else:
595 593 repo_setting.ui_active = active
596 594 Session().add(repo_setting)
597 595
598 596 def update_global_hook_settings(self, data):
599 597 for section, key in self.HOOKS_SETTINGS:
600 598 data_key = self._get_form_ui_key(section, key)
601 599 if data_key not in data:
602 600 raise ValueError(
603 601 'The given data does not contain {} key'.format(data_key))
604 602 active = data.get(data_key)
605 603 repo_setting = self.global_settings.get_ui_by_section_and_key(
606 604 section, key)
607 605 repo_setting.ui_active = active
608 606 Session().add(repo_setting)
609 607
610 608 @assert_repo_settings
611 609 def create_or_update_repo_pr_settings(self, data):
612 610 return self._create_or_update_general_settings(
613 611 self.repo_settings, data)
614 612
615 613 def create_or_update_global_pr_settings(self, data):
616 614 return self._create_or_update_general_settings(
617 615 self.global_settings, data)
618 616
619 617 @assert_repo_settings
620 618 def create_repo_svn_settings(self, data):
621 619 return self._create_svn_settings(self.repo_settings, data)
622 620
623 621 def _set_evolution(self, settings, is_enabled):
624 622 if is_enabled:
625 623 # if evolve is active set evolution=all
626 624
627 625 self._create_or_update_ui(
628 626 settings, *('experimental', 'evolution'), value='all',
629 627 active=True)
630 628 self._create_or_update_ui(
631 629 settings, *('experimental', 'evolution.exchange'), value='yes',
632 630 active=True)
633 631 # if evolve is active set topics server support
634 632 self._create_or_update_ui(
635 633 settings, *('extensions', 'topic'), value='',
636 634 active=True)
637 635
638 636 else:
639 637 self._create_or_update_ui(
640 638 settings, *('experimental', 'evolution'), value='',
641 639 active=False)
642 640 self._create_or_update_ui(
643 641 settings, *('experimental', 'evolution.exchange'), value='no',
644 642 active=False)
645 643 self._create_or_update_ui(
646 644 settings, *('extensions', 'topic'), value='',
647 645 active=False)
648 646
649 647 @assert_repo_settings
650 648 def create_or_update_repo_hg_settings(self, data):
651 649 largefiles, phases, evolve = \
652 650 self.HG_SETTINGS[:3]
653 651 largefiles_key, phases_key, evolve_key = \
654 652 self._get_settings_keys(self.HG_SETTINGS[:3], data)
655 653
656 654 self._create_or_update_ui(
657 655 self.repo_settings, *largefiles, value='',
658 656 active=data[largefiles_key])
659 657 self._create_or_update_ui(
660 658 self.repo_settings, *evolve, value='',
661 659 active=data[evolve_key])
662 660 self._set_evolution(self.repo_settings, is_enabled=data[evolve_key])
663 661
664 662 self._create_or_update_ui(
665 663 self.repo_settings, *phases, value=safe_str(data[phases_key]))
666 664
667 665 def create_or_update_global_hg_settings(self, data):
668 666 largefiles, largefiles_store, phases, hgsubversion, evolve \
669 667 = self.GLOBAL_HG_SETTINGS[:5]
670 668 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
671 669 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS[:5], data)
672 670
673 671 self._create_or_update_ui(
674 672 self.global_settings, *largefiles, value='',
675 673 active=data[largefiles_key])
676 674 self._create_or_update_ui(
677 675 self.global_settings, *largefiles_store, value=data[largefiles_store_key])
678 676 self._create_or_update_ui(
679 677 self.global_settings, *phases, value=safe_str(data[phases_key]))
680 678 self._create_or_update_ui(
681 679 self.global_settings, *hgsubversion, active=data[subversion_key])
682 680 self._create_or_update_ui(
683 681 self.global_settings, *evolve, value='',
684 682 active=data[evolve_key])
685 683 self._set_evolution(self.global_settings, is_enabled=data[evolve_key])
686 684
687 685 def create_or_update_repo_git_settings(self, data):
688 686 # NOTE(marcink): # comma makes unpack work properly
689 687 lfs_enabled, \
690 688 = self.GIT_SETTINGS
691 689
692 690 lfs_enabled_key, \
693 691 = self._get_settings_keys(self.GIT_SETTINGS, data)
694 692
695 693 self._create_or_update_ui(
696 694 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
697 695 active=data[lfs_enabled_key])
698 696
699 697 def create_or_update_global_git_settings(self, data):
700 698 lfs_enabled, lfs_store_location \
701 699 = self.GLOBAL_GIT_SETTINGS
702 700 lfs_enabled_key, lfs_store_location_key \
703 701 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
704 702
705 703 self._create_or_update_ui(
706 704 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
707 705 active=data[lfs_enabled_key])
708 706 self._create_or_update_ui(
709 707 self.global_settings, *lfs_store_location,
710 708 value=data[lfs_store_location_key])
711 709
712 710 def create_or_update_global_svn_settings(self, data):
713 711 # branch/tags patterns
714 712 self._create_svn_settings(self.global_settings, data)
715 713
716 714 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
717 715 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
718 716 self.GLOBAL_SVN_SETTINGS, data)
719 717
720 718 self._create_or_update_ui(
721 719 self.global_settings, *http_requests_enabled,
722 720 value=safe_str(data[http_requests_enabled_key]))
723 721 self._create_or_update_ui(
724 722 self.global_settings, *http_server_url,
725 723 value=data[http_server_url_key])
726 724
727 725 def update_global_ssl_setting(self, value):
728 726 self._create_or_update_ui(
729 727 self.global_settings, *self.SSL_SETTING, value=value)
730 728
731 729 def update_global_path_setting(self, value):
732 730 self._create_or_update_ui(
733 731 self.global_settings, *self.PATH_SETTING, value=value)
734 732
735 733 @assert_repo_settings
736 734 def delete_repo_svn_pattern(self, id_):
737 735 ui = self.repo_settings.UiDbModel.get(id_)
738 736 if ui and ui.repository.repo_name == self.repo_settings.repo:
739 737 # only delete if it's the same repo as initialized settings
740 738 self.repo_settings.delete_ui(id_)
741 739 else:
742 740 # raise error as if we wouldn't find this option
743 741 self.repo_settings.delete_ui(-1)
744 742
745 743 def delete_global_svn_pattern(self, id_):
746 744 self.global_settings.delete_ui(id_)
747 745
748 746 @assert_repo_settings
749 747 def get_repo_ui_settings(self, section=None, key=None):
750 748 global_uis = self.global_settings.get_ui(section, key)
751 749 repo_uis = self.repo_settings.get_ui(section, key)
752 750
753 751 filtered_repo_uis = self._filter_ui_settings(repo_uis)
754 752 filtered_repo_uis_keys = [
755 753 (s.section, s.key) for s in filtered_repo_uis]
756 754
757 755 def _is_global_ui_filtered(ui):
758 756 return (
759 757 (ui.section, ui.key) in filtered_repo_uis_keys
760 758 or ui.section in self._svn_sections)
761 759
762 760 filtered_global_uis = [
763 761 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
764 762
765 763 return filtered_global_uis + filtered_repo_uis
766 764
767 765 def get_global_ui_settings(self, section=None, key=None):
768 766 return self.global_settings.get_ui(section, key)
769 767
770 768 def get_ui_settings_as_config_obj(self, section=None, key=None):
771 769 config = base.Config()
772 770
773 771 ui_settings = self.get_ui_settings(section=section, key=key)
774 772
775 773 for entry in ui_settings:
776 774 config.set(entry.section, entry.key, entry.value)
777 775
778 776 return config
779 777
780 778 def get_ui_settings(self, section=None, key=None):
781 779 if not self.repo_settings or self.inherit_global_settings:
782 780 return self.get_global_ui_settings(section, key)
783 781 else:
784 782 return self.get_repo_ui_settings(section, key)
785 783
786 784 def get_svn_patterns(self, section=None):
787 785 if not self.repo_settings:
788 786 return self.get_global_ui_settings(section)
789 787 else:
790 788 return self.get_repo_ui_settings(section)
791 789
792 790 @assert_repo_settings
793 791 def get_repo_general_settings(self):
794 792 global_settings = self.global_settings.get_all_settings()
795 793 repo_settings = self.repo_settings.get_all_settings()
796 794 filtered_repo_settings = self._filter_general_settings(repo_settings)
797 795 global_settings.update(filtered_repo_settings)
798 796 return global_settings
799 797
800 798 def get_global_general_settings(self):
801 799 return self.global_settings.get_all_settings()
802 800
803 801 def get_general_settings(self):
804 802 if not self.repo_settings or self.inherit_global_settings:
805 803 return self.get_global_general_settings()
806 804 else:
807 805 return self.get_repo_general_settings()
808 806
809 807 def get_repos_location(self):
810 808 return self.global_settings.get_ui_by_key('/').ui_value
811 809
812 810 def _filter_ui_settings(self, settings):
813 811 filtered_settings = [
814 812 s for s in settings if self._should_keep_setting(s)]
815 813 return filtered_settings
816 814
817 815 def _should_keep_setting(self, setting):
818 816 keep = (
819 817 (setting.section, setting.key) in self._ui_settings or
820 818 setting.section in self._svn_sections)
821 819 return keep
822 820
823 821 def _filter_general_settings(self, settings):
824 822 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
825 823 return {
826 824 k: settings[k]
827 825 for k in settings if k in keys}
828 826
829 827 def _collect_all_settings(self, global_=False):
830 828 settings = self.global_settings if global_ else self.repo_settings
831 829 result = {}
832 830
833 831 for section, key in self._ui_settings:
834 832 ui = settings.get_ui_by_section_and_key(section, key)
835 833 result_key = self._get_form_ui_key(section, key)
836 834
837 835 if ui:
838 836 if section in ('hooks', 'extensions'):
839 837 result[result_key] = ui.ui_active
840 838 elif result_key in ['vcs_git_lfs_enabled']:
841 839 result[result_key] = ui.ui_active
842 840 else:
843 841 result[result_key] = ui.ui_value
844 842
845 843 for name in self.GENERAL_SETTINGS:
846 844 setting = settings.get_setting_by_name(name)
847 845 if setting:
848 846 result_key = 'rhodecode_{}'.format(name)
849 847 result[result_key] = setting.app_settings_value
850 848
851 849 return result
852 850
853 851 def _get_form_ui_key(self, section, key):
854 852 return '{section}_{key}'.format(
855 853 section=section, key=key.replace('.', '_'))
856 854
857 855 def _create_or_update_ui(
858 856 self, settings, section, key, value=None, active=None):
859 857 ui = settings.get_ui_by_section_and_key(section, key)
860 858 if not ui:
861 859 active = True if active is None else active
862 860 settings.create_ui_section_value(
863 861 section, value, key=key, active=active)
864 862 else:
865 863 if active is not None:
866 864 ui.ui_active = active
867 865 if value is not None:
868 866 ui.ui_value = value
869 867 Session().add(ui)
870 868
871 869 def _create_svn_settings(self, settings, data):
872 870 svn_settings = {
873 871 'new_svn_branch': self.SVN_BRANCH_SECTION,
874 872 'new_svn_tag': self.SVN_TAG_SECTION
875 873 }
876 874 for key in svn_settings:
877 875 if data.get(key):
878 876 settings.create_ui_section_value(svn_settings[key], data[key])
879 877
880 878 def _create_or_update_general_settings(self, settings, data):
881 879 for name in self.GENERAL_SETTINGS:
882 880 data_key = 'rhodecode_{}'.format(name)
883 881 if data_key not in data:
884 882 raise ValueError(
885 883 'The given data does not contain {} key'.format(data_key))
886 884 setting = settings.create_or_update_setting(
887 885 name, data[data_key], 'bool')
888 886 Session().add(setting)
889 887
890 888 def _get_settings_keys(self, settings, data):
891 889 data_keys = [self._get_form_ui_key(*s) for s in settings]
892 890 for data_key in data_keys:
893 891 if data_key not in data:
894 892 raise ValueError(
895 893 'The given data does not contain {} key'.format(data_key))
896 894 return data_keys
897 895
898 896 def create_largeobjects_dirs_if_needed(self, repo_store_path):
899 897 """
900 898 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
901 899 does a repository scan if enabled in the settings.
902 900 """
903 901
904 902 from rhodecode.lib.vcs.backends.hg import largefiles_store
905 903 from rhodecode.lib.vcs.backends.git import lfs_store
906 904
907 905 paths = [
908 906 largefiles_store(repo_store_path),
909 907 lfs_store(repo_store_path)]
910 908
911 909 for path in paths:
912 910 if os.path.isdir(path):
913 911 continue
914 912 if os.path.isfile(path):
915 913 continue
916 914 # not a file nor dir, we try to create it
917 915 try:
918 916 os.makedirs(path)
919 917 except Exception:
920 918 log.warning('Failed to create largefiles dir:%s', path)
General Comments 0
You need to be logged in to leave comments. Login now