middleware.py
800 lines
| 26.8 KiB
| text/x-python
|
PythonLexer
r1 | # -*- coding: utf-8 -*- | |||
r4306 | # Copyright (C) 2010-2020 RhodeCode GmbH | |||
r1 | # | |||
# This program is free software: you can redistribute it and/or modify | ||||
# it under the terms of the GNU Affero General Public License, version 3 | ||||
# (only), as published by the Free Software Foundation. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU Affero General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
# | ||||
# This program is dual-licensed. If you wish to learn more about the | ||||
# RhodeCode Enterprise Edition, including its added features, Support services, | ||||
# and proprietary license terms, please see https://rhodecode.com/licenses/ | ||||
r2818 | import os | |||
r2907 | import sys | |||
r1 | import logging | |||
r2321 | import collections | |||
r2845 | import tempfile | |||
r3238 | import time | |||
r1 | ||||
from paste.gzipper import make_gzip_middleware | ||||
r2930 | import pyramid.events | |||
r2351 | from pyramid.wsgi import wsgiapp | |||
r1 | from pyramid.authorization import ACLAuthorizationPolicy | |||
from pyramid.config import Configurator | ||||
from pyramid.settings import asbool, aslist | ||||
Martin Bornhold
|
r945 | from pyramid.httpexceptions import ( | ||
r2351 | HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound) | |||
Martin Bornhold
|
r595 | from pyramid.renderers import render_to_response | ||
r1785 | ||||
r669 | from rhodecode.model import meta | |||
r121 | from rhodecode.config import patches | |||
r2114 | from rhodecode.config import utils as config_utils | |||
r2351 | from rhodecode.config.environment import load_pyramid_environment | |||
r1785 | ||||
r2930 | import rhodecode.events | |||
r2351 | from rhodecode.lib.middleware.vcs import VCSMiddleware | |||
r2794 | from rhodecode.lib.request import Request | |||
r1785 | from rhodecode.lib.vcs import VCSCommunicationError | |||
from rhodecode.lib.exceptions import VCSServerUnavailable | ||||
r1 | from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled | |||
from rhodecode.lib.middleware.https_fixup import HttpsFixup | ||||
from rhodecode.lib.plugins.utils import register_rhodecode_plugin | ||||
r1785 | from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict | |||
r2907 | from rhodecode.lib.exc_tracking import store_exception | |||
r1392 | from rhodecode.subscribers import ( | |||
r1680 | scan_repositories_if_enabled, write_js_routes_if_enabled, | |||
r4548 | write_metadata_if_needed, write_usage_data) | |||
r4792 | from rhodecode.lib.statsd_client import StatsdClient | |||
r1 | ||||
log = logging.getLogger(__name__) | ||||
r2351 | def is_http_error(response): | |||
# error which should have traceback | ||||
return response.status_code > 499 | ||||
r1 | ||||
r3241 | def should_load_all(): | |||
""" | ||||
Returns if all application components should be loaded. In some cases it's | ||||
desired to skip apps loading for faster shell script execution | ||||
""" | ||||
r3910 | ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER') | |||
if ssh_cmd: | ||||
return False | ||||
r3241 | return True | |||
r1 | def make_pyramid_app(global_config, **settings): | |||
""" | ||||
r2351 | Constructs the WSGI application based on Pyramid. | |||
r1 | ||||
Specials: | ||||
* The application can also be integrated like a plugin via the call to | ||||
`includeme`. This is accompanied with the other utility functions which | ||||
are called. Changing this should be done with great care to not break | ||||
cases when these fragments are assembled from another place. | ||||
""" | ||||
r2818 | ||||
# Allows to use format style "{ENV_NAME}" placeholders in the configuration. It | ||||
# will be replaced by the value of the environment variable "NAME" in this case. | ||||
r3238 | start_time = time.time() | |||
r4610 | log.info('Pyramid app config starting') | |||
r3238 | ||||
r4792 | # init and bootstrap StatsdClient | |||
StatsdClient.setup(settings) | ||||
r3270 | debug = asbool(global_config.get('debug')) | |||
if debug: | ||||
enable_debug() | ||||
r3238 | environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()} | |||
r2818 | ||||
global_config = _substitute_values(global_config, environ) | ||||
settings = _substitute_values(settings, environ) | ||||
r3431 | sanitize_settings_and_apply_defaults(global_config, settings) | |||
r2114 | ||||
r1 | config = Configurator(settings=settings) | |||
r4792 | # Init our statsd at very start | |||
config.registry.statsd = StatsdClient.statsd | ||||
r2114 | ||||
r2351 | # Apply compatibility patches | |||
patches.inspect_getargspec() | ||||
load_pyramid_environment(global_config, settings) | ||||
r116 | ||||
r2325 | # Static file view comes first | |||
r463 | includeme_first(config) | |||
r2325 | ||||
r1 | includeme(config) | |||
r1927 | ||||
r1 | pyramid_app = config.make_wsgi_app() | |||
pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config) | ||||
r619 | pyramid_app.config = config | |||
r669 | ||||
r2359 | config.configure_celery(global_config['__file__']) | |||
r4610 | ||||
r669 | # creating the app uses a connection - return it after we are done | |||
meta.Session.remove() | ||||
r4792 | statsd = StatsdClient.statsd | |||
r3238 | total_time = time.time() - start_time | |||
r3240 | log.info('Pyramid app `%s` created and configured in %.2fs', | |||
pyramid_app.func_name, total_time) | ||||
r4792 | if statsd: | |||
r4806 | elapsed_time_ms = round(1000.0 * total_time) # use ms only rounded time | |||
r4792 | statsd.timing('rhodecode_app_bootstrap_timing', elapsed_time_ms, tags=[ | |||
"pyramid_app:{}".format(pyramid_app.func_name) | ||||
r4806 | ], use_decimals=False) | |||
r1 | return pyramid_app | |||
r2351 | def not_found_view(request): | |||
Martin Bornhold
|
r581 | """ | ||
Martin Bornhold
|
r595 | This creates the view which should be registered as not-found-view to | ||
r2351 | pyramid. | |||
Martin Bornhold
|
r581 | """ | ||
r2351 | if not getattr(request, 'vcs_call', None): | |||
# handle like regular case with our error_handler | ||||
return error_handler(HTTPNotFound(), request) | ||||
Martin Bornhold
|
r595 | |||
r2351 | # handle not found view as a vcs call | |||
settings = request.registry.settings | ||||
ae_client = getattr(request, 'ae_client', None) | ||||
vcs_app = VCSMiddleware( | ||||
HTTPNotFound(), request.registry, settings, | ||||
appenlight_client=ae_client) | ||||
Martin Bornhold
|
r609 | |||
r2351 | return wsgiapp(vcs_app)(None, request) | |||
r1 | ||||
r194 | def error_handler(exception, request): | |||
r1496 | import rhodecode | |||
r1748 | from rhodecode.lib import helpers | |||
r4768 | from rhodecode.lib.utils2 import str2bool | |||
r187 | ||||
r1496 | rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode' | |||
r187 | ||||
r194 | base_response = HTTPInternalServerError() | |||
# prefer original exception for the response since it may have headers set | ||||
r1499 | if isinstance(exception, HTTPException): | |||
r194 | base_response = exception | |||
r1785 | elif isinstance(exception, VCSCommunicationError): | |||
base_response = VCSServerUnavailable() | ||||
r194 | ||||
r1314 | if is_http_error(base_response): | |||
log.exception( | ||||
'error occurred handling this request for path: %s', request.path) | ||||
r2115 | error_explanation = base_response.explanation or str(base_response) | |||
if base_response.status_code == 404: | ||||
r3321 | error_explanation += " Optionally you don't have permission to access this page." | |||
r187 | c = AttributeDict() | |||
r194 | c.error_message = base_response.status | |||
r2115 | c.error_explanation = error_explanation | |||
r187 | c.visual = AttributeDict() | |||
c.visual.rhodecode_support_url = ( | ||||
request.registry.settings.get('rhodecode_support_url') or | ||||
request.route_url('rhodecode_support') | ||||
) | ||||
c.redirect_time = 0 | ||||
r1496 | c.rhodecode_name = rhodecode_title | |||
r187 | if not c.rhodecode_name: | |||
c.rhodecode_name = 'Rhodecode' | ||||
r683 | c.causes = [] | |||
r2116 | if is_http_error(base_response): | |||
c.causes.append('Server is overloaded.') | ||||
c.causes.append('Server database connection is lost.') | ||||
c.causes.append('Server expected unhandled error.') | ||||
r683 | if hasattr(base_response, 'causes'): | |||
c.causes = base_response.causes | ||||
r2116 | ||||
r1908 | c.messages = helpers.flash.pop_messages(request=request) | |||
r2907 | ||||
exc_info = sys.exc_info() | ||||
c.exception_id = id(exc_info) | ||||
c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \ | ||||
or base_response.status_code > 499 | ||||
c.exception_id_url = request.route_url( | ||||
'admin_settings_exception_tracker_show', exception_id=c.exception_id) | ||||
if c.show_exception_id: | ||||
store_exception(c.exception_id, exc_info) | ||||
r4768 | c.exception_debug = str2bool(rhodecode.CONFIG.get('debug')) | |||
c.exception_config_ini = rhodecode.CONFIG.get('__file__') | ||||
r2907 | ||||
r187 | response = render_to_response( | |||
r1748 | '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request, | |||
r190 | response=base_response) | |||
r4808 | statsd = request.registry.statsd | |||
if statsd and base_response.status_code > 499: | ||||
exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__) | ||||
statsd.incr('rhodecode_exception_total', | ||||
tags=["exc_source:web", | ||||
"http_code:{}".format(base_response.status_code), | ||||
"type:{}".format(exc_type)]) | ||||
r187 | return response | |||
r2326 | def includeme_first(config): | |||
# redirect automatic browser favicon.ico requests to correct place | ||||
def favicon_redirect(context, request): | ||||
return HTTPFound( | ||||
request.static_path('rhodecode:public/images/favicon.ico')) | ||||
config.add_view(favicon_redirect, route_name='favicon') | ||||
config.add_route('favicon', '/favicon.ico') | ||||
def robots_redirect(context, request): | ||||
return HTTPFound( | ||||
request.static_path('rhodecode:public/robots.txt')) | ||||
config.add_view(robots_redirect, route_name='robots') | ||||
config.add_route('robots', '/robots.txt') | ||||
config.add_static_view( | ||||
'_static/deform', 'deform:static') | ||||
config.add_static_view( | ||||
'_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24) | ||||
r4602 | def includeme(config, auth_resources=None): | |||
r4621 | from rhodecode.lib.celerylib.loader import configure_celery | |||
r3238 | log.debug('Initializing main includeme from %s', os.path.basename(__file__)) | |||
r1 | settings = config.registry.settings | |||
r2794 | config.set_request_factory(Request) | |||
r1 | ||||
r470 | # plugin information | |||
r2321 | config.registry.rhodecode_plugins = collections.OrderedDict() | |||
r470 | ||||
config.add_directive( | ||||
'register_rhodecode_plugin', register_rhodecode_plugin) | ||||
r2359 | config.add_directive('configure_celery', configure_celery) | |||
r194 | if asbool(settings.get('appenlight', 'false')): | |||
config.include('appenlight_client.ext.pyramid_tween') | ||||
r3241 | load_all = should_load_all() | |||
r1 | # Includes which are required. The application would fail without them. | |||
config.include('pyramid_mako') | ||||
r3748 | config.include('rhodecode.lib.rc_beaker') | |||
r2845 | config.include('rhodecode.lib.rc_cache') | |||
r3238 | config.include('rhodecode.apps._base.navigation') | |||
config.include('rhodecode.apps._base.subscribers') | ||||
config.include('rhodecode.tweens') | ||||
r3910 | config.include('rhodecode.authentication') | |||
r3238 | ||||
r3910 | if load_all: | |||
r4602 | ce_auth_resources = [ | |||
'rhodecode.authentication.plugins.auth_crowd', | ||||
'rhodecode.authentication.plugins.auth_headers', | ||||
'rhodecode.authentication.plugins.auth_jasig_cas', | ||||
'rhodecode.authentication.plugins.auth_ldap', | ||||
'rhodecode.authentication.plugins.auth_pam', | ||||
'rhodecode.authentication.plugins.auth_rhodecode', | ||||
'rhodecode.authentication.plugins.auth_token', | ||||
] | ||||
r3241 | # load CE authentication plugins | |||
r4602 | ||||
if auth_resources: | ||||
ce_auth_resources.extend(auth_resources) | ||||
for resource in ce_auth_resources: | ||||
config.include(resource) | ||||
r3241 | ||||
# Auto discover authentication plugins and include their configuration. | ||||
r4108 | if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')): | |||
from rhodecode.authentication import discover_legacy_plugins | ||||
discover_legacy_plugins(config) | ||||
r3241 | ||||
r1502 | # apps | |||
r3241 | if load_all: | |||
r4610 | config.include('rhodecode.api') | |||
r3910 | config.include('rhodecode.apps._base') | |||
r4026 | config.include('rhodecode.apps.hovercards') | |||
r3241 | config.include('rhodecode.apps.ops') | |||
config.include('rhodecode.apps.channelstream') | ||||
r3453 | config.include('rhodecode.apps.file_store') | |||
r4610 | config.include('rhodecode.apps.admin') | |||
r3241 | config.include('rhodecode.apps.login') | |||
config.include('rhodecode.apps.home') | ||||
config.include('rhodecode.apps.journal') | ||||
r4610 | ||||
r3241 | config.include('rhodecode.apps.repository') | |||
config.include('rhodecode.apps.repo_group') | ||||
config.include('rhodecode.apps.user_group') | ||||
config.include('rhodecode.apps.search') | ||||
config.include('rhodecode.apps.user_profile') | ||||
config.include('rhodecode.apps.user_group_profile') | ||||
config.include('rhodecode.apps.my_account') | ||||
r4610 | config.include('rhodecode.apps.gist') | |||
r3241 | config.include('rhodecode.apps.svn_support') | |||
config.include('rhodecode.apps.ssh_support') | ||||
config.include('rhodecode.apps.debug_style') | ||||
r4610 | ||||
if load_all: | ||||
config.include('rhodecode.integrations') | ||||
r1503 | ||||
r3238 | config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True) | |||
r1303 | config.add_translation_dirs('rhodecode:i18n/') | |||
settings['default_locale_name'] = settings.get('lang', 'en') | ||||
Martin Bornhold
|
r580 | # Add subscribers. | ||
r3910 | if load_all: | |||
config.add_subscriber(scan_repositories_if_enabled, | ||||
pyramid.events.ApplicationCreated) | ||||
config.add_subscriber(write_metadata_if_needed, | ||||
pyramid.events.ApplicationCreated) | ||||
r4473 | config.add_subscriber(write_usage_data, | |||
pyramid.events.ApplicationCreated) | ||||
r3910 | config.add_subscriber(write_js_routes_if_enabled, | |||
pyramid.events.ApplicationCreated) | ||||
r1789 | ||||
r2326 | # request custom methods | |||
config.add_request_method( | ||||
'rhodecode.lib.partial_renderer.get_partial_renderer', | ||||
'get_partial_renderer') | ||||
r3874 | config.add_request_method( | |||
'rhodecode.lib.request_counter.get_request_counter', | ||||
'request_count') | ||||
r1 | # Set the authorization policy. | |||
authz_policy = ACLAuthorizationPolicy() | ||||
config.set_authorization_policy(authz_policy) | ||||
# Set the default renderer for HTML templates to mako. | ||||
config.add_mako_renderer('.html') | ||||
r1664 | config.add_renderer( | |||
name='json_ext', | ||||
factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json') | ||||
r4500 | config.add_renderer( | |||
name='string_html', | ||||
factory='rhodecode.lib.string_renderer.html') | ||||
r1 | # include RhodeCode plugins | |||
includes = aslist(settings.get('rhodecode.includes', [])) | ||||
for inc in includes: | ||||
config.include(inc) | ||||
r2351 | # custom not found view, if our pyramid app doesn't know how to handle | |||
# the request pass it to potential VCS handling ap | ||||
config.add_notfound_view(not_found_view) | ||||
r449 | if not settings.get('debugtoolbar.enabled', False): | |||
r1912 | # disabled debugtoolbar handle all exceptions via the error_handlers | |||
r449 | config.add_view(error_handler, context=Exception) | |||
r2351 | # all errors including 403/404/50X | |||
r449 | config.add_view(error_handler, context=HTTPError) | |||
r1 | ||||
def wrap_app_in_wsgi_middlewares(pyramid_app, config): | ||||
""" | ||||
Apply outer WSGI middlewares around the application. | ||||
""" | ||||
r2921 | registry = config.registry | |||
settings = registry.settings | ||||
r1 | ||||
r181 | # enable https redirects based on HTTP_X_URL_SCHEME set by proxy | |||
pyramid_app = HttpsFixup(pyramid_app, settings) | ||||
r2351 | pyramid_app, _ae_client = wrap_in_appenlight_if_enabled( | |||
pyramid_app, settings) | ||||
r2921 | registry.ae_client = _ae_client | |||
r194 | ||||
Martin Bornhold
|
r598 | if settings['gzip_responses']: | ||
r1 | pyramid_app = make_gzip_middleware( | |||
pyramid_app, settings, compress_level=1) | ||||
r669 | # this should be the outer most middleware in the wsgi stack since | |||
# middleware like Routes make database calls | ||||
def pyramid_app_with_cleanup(environ, start_response): | ||||
try: | ||||
return pyramid_app(environ, start_response) | ||||
finally: | ||||
# Dispose current database session and rollback uncommitted | ||||
# transactions. | ||||
meta.Session.remove() | ||||
# In a single threaded mode server, on non sqlite db we should have | ||||
# '0 Current Checked out connections' at the end of a request, | ||||
# if not, then something, somewhere is leaving a connection open | ||||
pool = meta.Base.metadata.bind.engine.pool | ||||
log.debug('sa pool status: %s', pool.status()) | ||||
r2922 | log.debug('Request processing finalized') | |||
r669 | ||||
return pyramid_app_with_cleanup | ||||
r1 | ||||
r3431 | def sanitize_settings_and_apply_defaults(global_config, settings): | |||
r1 | """ | |||
Applies settings defaults and does all type conversion. | ||||
We would move all settings parsing and preparation into this place, so that | ||||
we have only one place left which deals with this part. The remaining parts | ||||
of the application would start to rely fully on well prepared settings. | ||||
This piece would later be split up per topic to avoid a big fat monster | ||||
function. | ||||
""" | ||||
r2316 | settings.setdefault('rhodecode.edition', 'Community Edition') | |||
r4516 | settings.setdefault('rhodecode.edition_id', 'CE') | |||
r2316 | ||||
if 'mako.default_filters' not in settings: | ||||
# set custom default filters if we don't have it defined | ||||
settings['mako.imports'] = 'from rhodecode.lib.base import h_filter' | ||||
settings['mako.default_filters'] = 'h_filter' | ||||
if 'mako.directories' not in settings: | ||||
mako_directories = settings.setdefault('mako.directories', [ | ||||
# Base templates of the original application | ||||
'rhodecode:templates', | ||||
]) | ||||
log.debug( | ||||
"Using the following Mako template directories: %s", | ||||
mako_directories) | ||||
r1 | ||||
r3867 | # NOTE(marcink): fix redis requirement for schema of connection since 3.X | |||
if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis': | ||||
raw_url = settings['beaker.session.url'] | ||||
if not raw_url.startswith(('redis://', 'rediss://', 'unix://')): | ||||
settings['beaker.session.url'] = 'redis://' + raw_url | ||||
r1 | # Default includes, possible to change as a user | |||
r4157 | pyramid_includes = settings.setdefault('pyramid.includes', []) | |||
r1 | log.debug( | |||
"Using the following pyramid.includes: %s", | ||||
pyramid_includes) | ||||
# TODO: johbo: Re-think this, usually the call to config.include | ||||
# should allow to pass in a prefix. | ||||
settings.setdefault('rhodecode.api.url', '/_admin/api') | ||||
r3431 | settings.setdefault('__file__', global_config.get('__file__')) | |||
r1 | ||||
Martin Bornhold
|
r598 | # Sanitize generic settings. | ||
Martin Bornhold
|
r586 | _list_setting(settings, 'default_encoding', 'UTF-8') | ||
r118 | _bool_setting(settings, 'is_test', 'false') | |||
Martin Bornhold
|
r598 | _bool_setting(settings, 'gzip_responses', 'false') | ||
r1 | ||||
Martin Bornhold
|
r585 | # Call split out functions that sanitize settings for each topic. | ||
Martin Bornhold
|
r598 | _sanitize_appenlight_settings(settings) | ||
Martin Bornhold
|
r585 | _sanitize_vcs_settings(settings) | ||
r2845 | _sanitize_cache_settings(settings) | |||
Martin Bornhold
|
r585 | |||
r2114 | # configure instance id | |||
config_utils.set_instance_id(settings) | ||||
r1 | return settings | |||
r3270 | def enable_debug(): | |||
""" | ||||
Helper to enable debug on running instance | ||||
:return: | ||||
""" | ||||
import tempfile | ||||
import textwrap | ||||
import logging.config | ||||
ini_template = textwrap.dedent(""" | ||||
##################################### | ||||
### DEBUG LOGGING CONFIGURATION #### | ||||
##################################### | ||||
[loggers] | ||||
keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper | ||||
[handlers] | ||||
keys = console, console_sql | ||||
[formatters] | ||||
keys = generic, color_formatter, color_formatter_sql | ||||
############# | ||||
## LOGGERS ## | ||||
############# | ||||
[logger_root] | ||||
level = NOTSET | ||||
handlers = console | ||||
[logger_sqlalchemy] | ||||
level = INFO | ||||
handlers = console_sql | ||||
qualname = sqlalchemy.engine | ||||
propagate = 0 | ||||
[logger_beaker] | ||||
level = DEBUG | ||||
handlers = | ||||
qualname = beaker.container | ||||
propagate = 1 | ||||
[logger_rhodecode] | ||||
level = DEBUG | ||||
handlers = | ||||
qualname = rhodecode | ||||
propagate = 1 | ||||
[logger_ssh_wrapper] | ||||
level = DEBUG | ||||
handlers = | ||||
qualname = ssh_wrapper | ||||
propagate = 1 | ||||
[logger_celery] | ||||
level = DEBUG | ||||
handlers = | ||||
qualname = celery | ||||
############## | ||||
## HANDLERS ## | ||||
############## | ||||
[handler_console] | ||||
class = StreamHandler | ||||
args = (sys.stderr, ) | ||||
level = DEBUG | ||||
formatter = color_formatter | ||||
[handler_console_sql] | ||||
# "level = DEBUG" logs SQL queries and results. | ||||
# "level = INFO" logs SQL queries. | ||||
# "level = WARN" logs neither. (Recommended for production systems.) | ||||
class = StreamHandler | ||||
args = (sys.stderr, ) | ||||
level = WARN | ||||
formatter = color_formatter_sql | ||||
################ | ||||
## FORMATTERS ## | ||||
################ | ||||
[formatter_generic] | ||||
class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter | ||||
format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s | ||||
datefmt = %Y-%m-%d %H:%M:%S | ||||
[formatter_color_formatter] | ||||
class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter | ||||
format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s | ||||
datefmt = %Y-%m-%d %H:%M:%S | ||||
[formatter_color_formatter_sql] | ||||
class = rhodecode.lib.logging_formatter.ColorFormatterSql | ||||
format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | ||||
datefmt = %Y-%m-%d %H:%M:%S | ||||
""") | ||||
with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini', | ||||
delete=False) as f: | ||||
log.info('Saved Temporary DEBUG config at %s', f.name) | ||||
f.write(ini_template) | ||||
logging.config.fileConfig(f.name) | ||||
log.debug('DEBUG MODE ON') | ||||
os.remove(f.name) | ||||
Martin Bornhold
|
r598 | def _sanitize_appenlight_settings(settings): | ||
_bool_setting(settings, 'appenlight', 'false') | ||||
Martin Bornhold
|
r585 | def _sanitize_vcs_settings(settings): | ||
""" | ||||
Applies settings defaults and does type conversion for all VCS related | ||||
settings. | ||||
""" | ||||
_string_setting(settings, 'vcs.svn.compatible_version', '') | ||||
Martin Bornhold
|
r958 | _string_setting(settings, 'vcs.hooks.protocol', 'http') | ||
r2833 | _string_setting(settings, 'vcs.hooks.host', '127.0.0.1') | |||
Martin Bornhold
|
r958 | _string_setting(settings, 'vcs.scm_app_implementation', 'http') | ||
Martin Bornhold
|
r585 | _string_setting(settings, 'vcs.server', '') | ||
Martin Bornhold
|
r958 | _string_setting(settings, 'vcs.server.protocol', 'http') | ||
Martin Bornhold
|
r585 | _bool_setting(settings, 'startup.import_repos', 'false') | ||
_bool_setting(settings, 'vcs.hooks.direct_calls', 'false') | ||||
_bool_setting(settings, 'vcs.server.enable', 'true') | ||||
_bool_setting(settings, 'vcs.start_server', 'false') | ||||
_list_setting(settings, 'vcs.backends', 'hg, git, svn') | ||||
Martin Bornhold
|
r623 | _int_setting(settings, 'vcs.connection_timeout', 3600) | ||
Martin Bornhold
|
r963 | # Support legacy values of vcs.scm_app_implementation. Legacy | ||
r2958 | # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or | |||
# disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'. | ||||
Martin Bornhold
|
r963 | scm_app_impl = settings['vcs.scm_app_implementation'] | ||
r2958 | if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']: | |||
Martin Bornhold
|
r963 | settings['vcs.scm_app_implementation'] = 'http' | ||
Martin Bornhold
|
r623 | |||
r2845 | def _sanitize_cache_settings(settings): | |||
r3028 | temp_store = tempfile.gettempdir() | |||
default_cache_dir = os.path.join(temp_store, 'rc_cache') | ||||
r3008 | ||||
# save default, cache dir, and use it for all backends later. | ||||
default_cache_dir = _string_setting( | ||||
settings, | ||||
'cache_dir', | ||||
default_cache_dir, lower=False, default_when_empty=True) | ||||
# ensure we have our dir created | ||||
if not os.path.isdir(default_cache_dir): | ||||
r3268 | os.makedirs(default_cache_dir, mode=0o755) | |||
r3008 | ||||
r3019 | # exception store cache | |||
_string_setting( | ||||
settings, | ||||
r3024 | 'exception_tracker.store_path', | |||
r3028 | temp_store, lower=False, default_when_empty=True) | |||
r4276 | _bool_setting( | |||
settings, | ||||
'exception_tracker.send_email', | ||||
'false') | ||||
_string_setting( | ||||
settings, | ||||
'exception_tracker.email_prefix', | ||||
'[RHODECODE ERROR]', lower=False, default_when_empty=True) | ||||
r3019 | ||||
r2883 | # cache_perms | |||
_string_setting( | ||||
settings, | ||||
'rc_cache.cache_perms.backend', | ||||
r3008 | 'dogpile.cache.rc.file_namespace', lower=False) | |||
r2883 | _int_setting( | |||
settings, | ||||
'rc_cache.cache_perms.expiration_time', | ||||
60) | ||||
_string_setting( | ||||
settings, | ||||
'rc_cache.cache_perms.arguments.filename', | ||||
r3008 | os.path.join(default_cache_dir, 'rc_cache_1'), lower=False) | |||
r2845 | ||||
r2883 | # cache_repo | |||
_string_setting( | ||||
settings, | ||||
'rc_cache.cache_repo.backend', | ||||
r3008 | 'dogpile.cache.rc.file_namespace', lower=False) | |||
r2883 | _int_setting( | |||
settings, | ||||
'rc_cache.cache_repo.expiration_time', | ||||
60) | ||||
_string_setting( | ||||
settings, | ||||
'rc_cache.cache_repo.arguments.filename', | ||||
r3008 | os.path.join(default_cache_dir, 'rc_cache_2'), lower=False) | |||
r2845 | ||||
r2959 | # cache_license | |||
_string_setting( | ||||
settings, | ||||
'rc_cache.cache_license.backend', | ||||
r3008 | 'dogpile.cache.rc.file_namespace', lower=False) | |||
r2959 | _int_setting( | |||
settings, | ||||
'rc_cache.cache_license.expiration_time', | ||||
5*60) | ||||
_string_setting( | ||||
settings, | ||||
'rc_cache.cache_license.arguments.filename', | ||||
r3008 | os.path.join(default_cache_dir, 'rc_cache_3'), lower=False) | |||
r2959 | ||||
r2932 | # cache_repo_longterm memory, 96H | |||
_string_setting( | ||||
settings, | ||||
'rc_cache.cache_repo_longterm.backend', | ||||
r3008 | 'dogpile.cache.rc.memory_lru', lower=False) | |||
r2932 | _int_setting( | |||
settings, | ||||
'rc_cache.cache_repo_longterm.expiration_time', | ||||
345600) | ||||
_int_setting( | ||||
settings, | ||||
'rc_cache.cache_repo_longterm.max_size', | ||||
10000) | ||||
r2883 | # sql_cache_short | |||
_string_setting( | ||||
settings, | ||||
'rc_cache.sql_cache_short.backend', | ||||
r3008 | 'dogpile.cache.rc.memory_lru', lower=False) | |||
r2883 | _int_setting( | |||
settings, | ||||
'rc_cache.sql_cache_short.expiration_time', | ||||
30) | ||||
_int_setting( | ||||
settings, | ||||
'rc_cache.sql_cache_short.max_size', | ||||
10000) | ||||
r2846 | ||||
r2845 | ||||
Martin Bornhold
|
r623 | def _int_setting(settings, name, default): | ||
settings[name] = int(settings.get(name, default)) | ||||
r3008 | return settings[name] | |||
Martin Bornhold
|
r585 | |||
r1 | def _bool_setting(settings, name, default): | |||
r2351 | input_val = settings.get(name, default) | |||
if isinstance(input_val, unicode): | ||||
input_val = input_val.encode('utf8') | ||||
settings[name] = asbool(input_val) | ||||
r3008 | return settings[name] | |||
Martin Bornhold
|
r583 | |||
def _list_setting(settings, name, default): | ||||
raw_value = settings.get(name, default) | ||||
Martin Bornhold
|
r604 | old_separator = ',' | ||
if old_separator in raw_value: | ||||
# If we get a comma separated list, pass it to our own function. | ||||
settings[name] = rhodecode_aslist(raw_value, sep=old_separator) | ||||
else: | ||||
# Otherwise we assume it uses pyramids space/newline separation. | ||||
settings[name] = aslist(raw_value) | ||||
r3008 | return settings[name] | |||
Martin Bornhold
|
r584 | |||
r3008 | def _string_setting(settings, name, default, lower=True, default_when_empty=False): | |||
Martin Bornhold
|
r1003 | value = settings.get(name, default) | ||
r3008 | ||||
if default_when_empty and not value: | ||||
# use default value when value is empty | ||||
value = default | ||||
Martin Bornhold
|
r1003 | if lower: | ||
value = value.lower() | ||||
settings[name] = value | ||||
r3008 | return settings[name] | |||
r2818 | ||||
def _substitute_values(mapping, substitutions): | ||||
r3366 | result = {} | |||
r2986 | ||||
try: | ||||
r3366 | for key, value in mapping.items(): | |||
# initialize without substitution first | ||||
result[key] = value | ||||
r2986 | # Note: Cannot use regular replacements, since they would clash | |||
# with the implementation of ConfigParser. Using "format" instead. | ||||
r3366 | try: | |||
result[key] = value.format(**substitutions) | ||||
except KeyError as e: | ||||
env_var = '{}'.format(e.args[0]) | ||||
msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \ | ||||
'Make sure your environment has {var} set, or remove this ' \ | ||||
'variable from config file'.format(key=key, var=env_var) | ||||
if env_var.startswith('ENV_'): | ||||
raise ValueError(msg) | ||||
else: | ||||
log.warning(msg) | ||||
r3237 | except ValueError as e: | |||
log.warning('Failed to substitute ENV variable: %s', e) | ||||
result = mapping | ||||
r2986 | ||||
r2818 | return result | |||