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