##// END OF EJS Templates
debug: add new custom logging to track unique requests across systems.
marcink -
r2794:9b8d6c69 default
parent child Browse files
Show More
@@ -0,0 +1,29 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-2018 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from uuid import uuid4
22 from pyramid.decorator import reify
23 from pyramid.request import Request as _Request
24
25
26 class Request(_Request):
27 @reify
28 def req_id(self):
29 return str(uuid4())
@@ -1,438 +1,440 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 logging
21 import logging
22 import traceback
22 import traceback
23 import collections
23 import collections
24
24
25 from paste.gzipper import make_gzip_middleware
25 from paste.gzipper import make_gzip_middleware
26 from pyramid.wsgi import wsgiapp
26 from pyramid.wsgi import wsgiapp
27 from pyramid.authorization import ACLAuthorizationPolicy
27 from pyramid.authorization import ACLAuthorizationPolicy
28 from pyramid.config import Configurator
28 from pyramid.config import Configurator
29 from pyramid.settings import asbool, aslist
29 from pyramid.settings import asbool, aslist
30 from pyramid.httpexceptions import (
30 from pyramid.httpexceptions import (
31 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
31 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
32 from pyramid.events import ApplicationCreated
32 from pyramid.events import ApplicationCreated
33 from pyramid.renderers import render_to_response
33 from pyramid.renderers import render_to_response
34
34
35 from rhodecode.model import meta
35 from rhodecode.model import meta
36 from rhodecode.config import patches
36 from rhodecode.config import patches
37 from rhodecode.config import utils as config_utils
37 from rhodecode.config import utils as config_utils
38 from rhodecode.config.environment import load_pyramid_environment
38 from rhodecode.config.environment import load_pyramid_environment
39
39
40 from rhodecode.lib.middleware.vcs import VCSMiddleware
40 from rhodecode.lib.middleware.vcs import VCSMiddleware
41 from rhodecode.lib.request import Request
41 from rhodecode.lib.vcs import VCSCommunicationError
42 from rhodecode.lib.vcs import VCSCommunicationError
42 from rhodecode.lib.exceptions import VCSServerUnavailable
43 from rhodecode.lib.exceptions import VCSServerUnavailable
43 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
44 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
44 from rhodecode.lib.middleware.https_fixup import HttpsFixup
45 from rhodecode.lib.middleware.https_fixup import HttpsFixup
45 from rhodecode.lib.celerylib.loader import configure_celery
46 from rhodecode.lib.celerylib.loader import configure_celery
46 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
47 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
47 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
48 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
48 from rhodecode.subscribers import (
49 from rhodecode.subscribers import (
49 scan_repositories_if_enabled, write_js_routes_if_enabled,
50 scan_repositories_if_enabled, write_js_routes_if_enabled,
50 write_metadata_if_needed, inject_app_settings)
51 write_metadata_if_needed, inject_app_settings)
51
52
52
53
53 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
54
55
55
56
56 def is_http_error(response):
57 def is_http_error(response):
57 # error which should have traceback
58 # error which should have traceback
58 return response.status_code > 499
59 return response.status_code > 499
59
60
60
61
61 def make_pyramid_app(global_config, **settings):
62 def make_pyramid_app(global_config, **settings):
62 """
63 """
63 Constructs the WSGI application based on Pyramid.
64 Constructs the WSGI application based on Pyramid.
64
65
65 Specials:
66 Specials:
66
67
67 * The application can also be integrated like a plugin via the call to
68 * The application can also be integrated like a plugin via the call to
68 `includeme`. This is accompanied with the other utility functions which
69 `includeme`. This is accompanied with the other utility functions which
69 are called. Changing this should be done with great care to not break
70 are called. Changing this should be done with great care to not break
70 cases when these fragments are assembled from another place.
71 cases when these fragments are assembled from another place.
71
72
72 """
73 """
73 sanitize_settings_and_apply_defaults(settings)
74 sanitize_settings_and_apply_defaults(settings)
74
75
75 config = Configurator(settings=settings)
76 config = Configurator(settings=settings)
76
77
77 # Apply compatibility patches
78 # Apply compatibility patches
78 patches.inspect_getargspec()
79 patches.inspect_getargspec()
79
80
80 load_pyramid_environment(global_config, settings)
81 load_pyramid_environment(global_config, settings)
81
82
82 # Static file view comes first
83 # Static file view comes first
83 includeme_first(config)
84 includeme_first(config)
84
85
85 includeme(config)
86 includeme(config)
86
87
87 pyramid_app = config.make_wsgi_app()
88 pyramid_app = config.make_wsgi_app()
88 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
89 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
89 pyramid_app.config = config
90 pyramid_app.config = config
90
91
91 config.configure_celery(global_config['__file__'])
92 config.configure_celery(global_config['__file__'])
92 # creating the app uses a connection - return it after we are done
93 # creating the app uses a connection - return it after we are done
93 meta.Session.remove()
94 meta.Session.remove()
94
95
95 log.info('Pyramid app %s created and configured.', pyramid_app)
96 log.info('Pyramid app %s created and configured.', pyramid_app)
96 return pyramid_app
97 return pyramid_app
97
98
98
99
99 def not_found_view(request):
100 def not_found_view(request):
100 """
101 """
101 This creates the view which should be registered as not-found-view to
102 This creates the view which should be registered as not-found-view to
102 pyramid.
103 pyramid.
103 """
104 """
104
105
105 if not getattr(request, 'vcs_call', None):
106 if not getattr(request, 'vcs_call', None):
106 # handle like regular case with our error_handler
107 # handle like regular case with our error_handler
107 return error_handler(HTTPNotFound(), request)
108 return error_handler(HTTPNotFound(), request)
108
109
109 # handle not found view as a vcs call
110 # handle not found view as a vcs call
110 settings = request.registry.settings
111 settings = request.registry.settings
111 ae_client = getattr(request, 'ae_client', None)
112 ae_client = getattr(request, 'ae_client', None)
112 vcs_app = VCSMiddleware(
113 vcs_app = VCSMiddleware(
113 HTTPNotFound(), request.registry, settings,
114 HTTPNotFound(), request.registry, settings,
114 appenlight_client=ae_client)
115 appenlight_client=ae_client)
115
116
116 return wsgiapp(vcs_app)(None, request)
117 return wsgiapp(vcs_app)(None, request)
117
118
118
119
119 def error_handler(exception, request):
120 def error_handler(exception, request):
120 import rhodecode
121 import rhodecode
121 from rhodecode.lib import helpers
122 from rhodecode.lib import helpers
122
123
123 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
124 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
124
125
125 base_response = HTTPInternalServerError()
126 base_response = HTTPInternalServerError()
126 # prefer original exception for the response since it may have headers set
127 # prefer original exception for the response since it may have headers set
127 if isinstance(exception, HTTPException):
128 if isinstance(exception, HTTPException):
128 base_response = exception
129 base_response = exception
129 elif isinstance(exception, VCSCommunicationError):
130 elif isinstance(exception, VCSCommunicationError):
130 base_response = VCSServerUnavailable()
131 base_response = VCSServerUnavailable()
131
132
132 if is_http_error(base_response):
133 if is_http_error(base_response):
133 log.exception(
134 log.exception(
134 'error occurred handling this request for path: %s', request.path)
135 'error occurred handling this request for path: %s', request.path)
135
136
136 error_explanation = base_response.explanation or str(base_response)
137 error_explanation = base_response.explanation or str(base_response)
137 if base_response.status_code == 404:
138 if base_response.status_code == 404:
138 error_explanation += " Or you don't have permission to access it."
139 error_explanation += " Or you don't have permission to access it."
139 c = AttributeDict()
140 c = AttributeDict()
140 c.error_message = base_response.status
141 c.error_message = base_response.status
141 c.error_explanation = error_explanation
142 c.error_explanation = error_explanation
142 c.visual = AttributeDict()
143 c.visual = AttributeDict()
143
144
144 c.visual.rhodecode_support_url = (
145 c.visual.rhodecode_support_url = (
145 request.registry.settings.get('rhodecode_support_url') or
146 request.registry.settings.get('rhodecode_support_url') or
146 request.route_url('rhodecode_support')
147 request.route_url('rhodecode_support')
147 )
148 )
148 c.redirect_time = 0
149 c.redirect_time = 0
149 c.rhodecode_name = rhodecode_title
150 c.rhodecode_name = rhodecode_title
150 if not c.rhodecode_name:
151 if not c.rhodecode_name:
151 c.rhodecode_name = 'Rhodecode'
152 c.rhodecode_name = 'Rhodecode'
152
153
153 c.causes = []
154 c.causes = []
154 if is_http_error(base_response):
155 if is_http_error(base_response):
155 c.causes.append('Server is overloaded.')
156 c.causes.append('Server is overloaded.')
156 c.causes.append('Server database connection is lost.')
157 c.causes.append('Server database connection is lost.')
157 c.causes.append('Server expected unhandled error.')
158 c.causes.append('Server expected unhandled error.')
158
159
159 if hasattr(base_response, 'causes'):
160 if hasattr(base_response, 'causes'):
160 c.causes = base_response.causes
161 c.causes = base_response.causes
161
162
162 c.messages = helpers.flash.pop_messages(request=request)
163 c.messages = helpers.flash.pop_messages(request=request)
163 c.traceback = traceback.format_exc()
164 c.traceback = traceback.format_exc()
164 response = render_to_response(
165 response = render_to_response(
165 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
166 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
166 response=base_response)
167 response=base_response)
167
168
168 return response
169 return response
169
170
170
171
171 def includeme_first(config):
172 def includeme_first(config):
172 # redirect automatic browser favicon.ico requests to correct place
173 # redirect automatic browser favicon.ico requests to correct place
173 def favicon_redirect(context, request):
174 def favicon_redirect(context, request):
174 return HTTPFound(
175 return HTTPFound(
175 request.static_path('rhodecode:public/images/favicon.ico'))
176 request.static_path('rhodecode:public/images/favicon.ico'))
176
177
177 config.add_view(favicon_redirect, route_name='favicon')
178 config.add_view(favicon_redirect, route_name='favicon')
178 config.add_route('favicon', '/favicon.ico')
179 config.add_route('favicon', '/favicon.ico')
179
180
180 def robots_redirect(context, request):
181 def robots_redirect(context, request):
181 return HTTPFound(
182 return HTTPFound(
182 request.static_path('rhodecode:public/robots.txt'))
183 request.static_path('rhodecode:public/robots.txt'))
183
184
184 config.add_view(robots_redirect, route_name='robots')
185 config.add_view(robots_redirect, route_name='robots')
185 config.add_route('robots', '/robots.txt')
186 config.add_route('robots', '/robots.txt')
186
187
187 config.add_static_view(
188 config.add_static_view(
188 '_static/deform', 'deform:static')
189 '_static/deform', 'deform:static')
189 config.add_static_view(
190 config.add_static_view(
190 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
191 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
191
192
192
193
193 def includeme(config):
194 def includeme(config):
194 settings = config.registry.settings
195 settings = config.registry.settings
196 config.set_request_factory(Request)
195
197
196 # plugin information
198 # plugin information
197 config.registry.rhodecode_plugins = collections.OrderedDict()
199 config.registry.rhodecode_plugins = collections.OrderedDict()
198
200
199 config.add_directive(
201 config.add_directive(
200 'register_rhodecode_plugin', register_rhodecode_plugin)
202 'register_rhodecode_plugin', register_rhodecode_plugin)
201
203
202 config.add_directive('configure_celery', configure_celery)
204 config.add_directive('configure_celery', configure_celery)
203
205
204 if asbool(settings.get('appenlight', 'false')):
206 if asbool(settings.get('appenlight', 'false')):
205 config.include('appenlight_client.ext.pyramid_tween')
207 config.include('appenlight_client.ext.pyramid_tween')
206
208
207 # Includes which are required. The application would fail without them.
209 # Includes which are required. The application would fail without them.
208 config.include('pyramid_mako')
210 config.include('pyramid_mako')
209 config.include('pyramid_beaker')
211 config.include('pyramid_beaker')
210 config.include('rhodecode.lib.caches')
212 config.include('rhodecode.lib.caches')
211
213
212 config.include('rhodecode.authentication')
214 config.include('rhodecode.authentication')
213 config.include('rhodecode.integrations')
215 config.include('rhodecode.integrations')
214
216
215 # apps
217 # apps
216 config.include('rhodecode.apps._base')
218 config.include('rhodecode.apps._base')
217 config.include('rhodecode.apps.ops')
219 config.include('rhodecode.apps.ops')
218
220
219 config.include('rhodecode.apps.admin')
221 config.include('rhodecode.apps.admin')
220 config.include('rhodecode.apps.channelstream')
222 config.include('rhodecode.apps.channelstream')
221 config.include('rhodecode.apps.login')
223 config.include('rhodecode.apps.login')
222 config.include('rhodecode.apps.home')
224 config.include('rhodecode.apps.home')
223 config.include('rhodecode.apps.journal')
225 config.include('rhodecode.apps.journal')
224 config.include('rhodecode.apps.repository')
226 config.include('rhodecode.apps.repository')
225 config.include('rhodecode.apps.repo_group')
227 config.include('rhodecode.apps.repo_group')
226 config.include('rhodecode.apps.user_group')
228 config.include('rhodecode.apps.user_group')
227 config.include('rhodecode.apps.search')
229 config.include('rhodecode.apps.search')
228 config.include('rhodecode.apps.user_profile')
230 config.include('rhodecode.apps.user_profile')
229 config.include('rhodecode.apps.user_group_profile')
231 config.include('rhodecode.apps.user_group_profile')
230 config.include('rhodecode.apps.my_account')
232 config.include('rhodecode.apps.my_account')
231 config.include('rhodecode.apps.svn_support')
233 config.include('rhodecode.apps.svn_support')
232 config.include('rhodecode.apps.ssh_support')
234 config.include('rhodecode.apps.ssh_support')
233 config.include('rhodecode.apps.gist')
235 config.include('rhodecode.apps.gist')
234
236
235 config.include('rhodecode.apps.debug_style')
237 config.include('rhodecode.apps.debug_style')
236 config.include('rhodecode.tweens')
238 config.include('rhodecode.tweens')
237 config.include('rhodecode.api')
239 config.include('rhodecode.api')
238
240
239 config.add_route(
241 config.add_route(
240 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
242 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
241
243
242 config.add_translation_dirs('rhodecode:i18n/')
244 config.add_translation_dirs('rhodecode:i18n/')
243 settings['default_locale_name'] = settings.get('lang', 'en')
245 settings['default_locale_name'] = settings.get('lang', 'en')
244
246
245 # Add subscribers.
247 # Add subscribers.
246 config.add_subscriber(inject_app_settings, ApplicationCreated)
248 config.add_subscriber(inject_app_settings, ApplicationCreated)
247 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
249 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
248 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
250 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
249 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
251 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
250
252
251 # events
253 # events
252 # TODO(marcink): this should be done when pyramid migration is finished
254 # TODO(marcink): this should be done when pyramid migration is finished
253 # config.add_subscriber(
255 # config.add_subscriber(
254 # 'rhodecode.integrations.integrations_event_handler',
256 # 'rhodecode.integrations.integrations_event_handler',
255 # 'rhodecode.events.RhodecodeEvent')
257 # 'rhodecode.events.RhodecodeEvent')
256
258
257 # request custom methods
259 # request custom methods
258 config.add_request_method(
260 config.add_request_method(
259 'rhodecode.lib.partial_renderer.get_partial_renderer',
261 'rhodecode.lib.partial_renderer.get_partial_renderer',
260 'get_partial_renderer')
262 'get_partial_renderer')
261
263
262 # Set the authorization policy.
264 # Set the authorization policy.
263 authz_policy = ACLAuthorizationPolicy()
265 authz_policy = ACLAuthorizationPolicy()
264 config.set_authorization_policy(authz_policy)
266 config.set_authorization_policy(authz_policy)
265
267
266 # Set the default renderer for HTML templates to mako.
268 # Set the default renderer for HTML templates to mako.
267 config.add_mako_renderer('.html')
269 config.add_mako_renderer('.html')
268
270
269 config.add_renderer(
271 config.add_renderer(
270 name='json_ext',
272 name='json_ext',
271 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
273 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
272
274
273 # include RhodeCode plugins
275 # include RhodeCode plugins
274 includes = aslist(settings.get('rhodecode.includes', []))
276 includes = aslist(settings.get('rhodecode.includes', []))
275 for inc in includes:
277 for inc in includes:
276 config.include(inc)
278 config.include(inc)
277
279
278 # custom not found view, if our pyramid app doesn't know how to handle
280 # custom not found view, if our pyramid app doesn't know how to handle
279 # the request pass it to potential VCS handling ap
281 # the request pass it to potential VCS handling ap
280 config.add_notfound_view(not_found_view)
282 config.add_notfound_view(not_found_view)
281 if not settings.get('debugtoolbar.enabled', False):
283 if not settings.get('debugtoolbar.enabled', False):
282 # disabled debugtoolbar handle all exceptions via the error_handlers
284 # disabled debugtoolbar handle all exceptions via the error_handlers
283 config.add_view(error_handler, context=Exception)
285 config.add_view(error_handler, context=Exception)
284
286
285 # all errors including 403/404/50X
287 # all errors including 403/404/50X
286 config.add_view(error_handler, context=HTTPError)
288 config.add_view(error_handler, context=HTTPError)
287
289
288
290
289 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
291 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
290 """
292 """
291 Apply outer WSGI middlewares around the application.
293 Apply outer WSGI middlewares around the application.
292 """
294 """
293 settings = config.registry.settings
295 settings = config.registry.settings
294
296
295 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
297 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
296 pyramid_app = HttpsFixup(pyramid_app, settings)
298 pyramid_app = HttpsFixup(pyramid_app, settings)
297
299
298 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
300 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
299 pyramid_app, settings)
301 pyramid_app, settings)
300 config.registry.ae_client = _ae_client
302 config.registry.ae_client = _ae_client
301
303
302 if settings['gzip_responses']:
304 if settings['gzip_responses']:
303 pyramid_app = make_gzip_middleware(
305 pyramid_app = make_gzip_middleware(
304 pyramid_app, settings, compress_level=1)
306 pyramid_app, settings, compress_level=1)
305
307
306 # this should be the outer most middleware in the wsgi stack since
308 # this should be the outer most middleware in the wsgi stack since
307 # middleware like Routes make database calls
309 # middleware like Routes make database calls
308 def pyramid_app_with_cleanup(environ, start_response):
310 def pyramid_app_with_cleanup(environ, start_response):
309 try:
311 try:
310 return pyramid_app(environ, start_response)
312 return pyramid_app(environ, start_response)
311 finally:
313 finally:
312 # Dispose current database session and rollback uncommitted
314 # Dispose current database session and rollback uncommitted
313 # transactions.
315 # transactions.
314 meta.Session.remove()
316 meta.Session.remove()
315
317
316 # In a single threaded mode server, on non sqlite db we should have
318 # In a single threaded mode server, on non sqlite db we should have
317 # '0 Current Checked out connections' at the end of a request,
319 # '0 Current Checked out connections' at the end of a request,
318 # if not, then something, somewhere is leaving a connection open
320 # if not, then something, somewhere is leaving a connection open
319 pool = meta.Base.metadata.bind.engine.pool
321 pool = meta.Base.metadata.bind.engine.pool
320 log.debug('sa pool status: %s', pool.status())
322 log.debug('sa pool status: %s', pool.status())
321
323
322 return pyramid_app_with_cleanup
324 return pyramid_app_with_cleanup
323
325
324
326
325 def sanitize_settings_and_apply_defaults(settings):
327 def sanitize_settings_and_apply_defaults(settings):
326 """
328 """
327 Applies settings defaults and does all type conversion.
329 Applies settings defaults and does all type conversion.
328
330
329 We would move all settings parsing and preparation into this place, so that
331 We would move all settings parsing and preparation into this place, so that
330 we have only one place left which deals with this part. The remaining parts
332 we have only one place left which deals with this part. The remaining parts
331 of the application would start to rely fully on well prepared settings.
333 of the application would start to rely fully on well prepared settings.
332
334
333 This piece would later be split up per topic to avoid a big fat monster
335 This piece would later be split up per topic to avoid a big fat monster
334 function.
336 function.
335 """
337 """
336
338
337 settings.setdefault('rhodecode.edition', 'Community Edition')
339 settings.setdefault('rhodecode.edition', 'Community Edition')
338
340
339 if 'mako.default_filters' not in settings:
341 if 'mako.default_filters' not in settings:
340 # set custom default filters if we don't have it defined
342 # set custom default filters if we don't have it defined
341 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
343 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
342 settings['mako.default_filters'] = 'h_filter'
344 settings['mako.default_filters'] = 'h_filter'
343
345
344 if 'mako.directories' not in settings:
346 if 'mako.directories' not in settings:
345 mako_directories = settings.setdefault('mako.directories', [
347 mako_directories = settings.setdefault('mako.directories', [
346 # Base templates of the original application
348 # Base templates of the original application
347 'rhodecode:templates',
349 'rhodecode:templates',
348 ])
350 ])
349 log.debug(
351 log.debug(
350 "Using the following Mako template directories: %s",
352 "Using the following Mako template directories: %s",
351 mako_directories)
353 mako_directories)
352
354
353 # Default includes, possible to change as a user
355 # Default includes, possible to change as a user
354 pyramid_includes = settings.setdefault('pyramid.includes', [
356 pyramid_includes = settings.setdefault('pyramid.includes', [
355 'rhodecode.lib.middleware.request_wrapper',
357 'rhodecode.lib.middleware.request_wrapper',
356 ])
358 ])
357 log.debug(
359 log.debug(
358 "Using the following pyramid.includes: %s",
360 "Using the following pyramid.includes: %s",
359 pyramid_includes)
361 pyramid_includes)
360
362
361 # TODO: johbo: Re-think this, usually the call to config.include
363 # TODO: johbo: Re-think this, usually the call to config.include
362 # should allow to pass in a prefix.
364 # should allow to pass in a prefix.
363 settings.setdefault('rhodecode.api.url', '/_admin/api')
365 settings.setdefault('rhodecode.api.url', '/_admin/api')
364
366
365 # Sanitize generic settings.
367 # Sanitize generic settings.
366 _list_setting(settings, 'default_encoding', 'UTF-8')
368 _list_setting(settings, 'default_encoding', 'UTF-8')
367 _bool_setting(settings, 'is_test', 'false')
369 _bool_setting(settings, 'is_test', 'false')
368 _bool_setting(settings, 'gzip_responses', 'false')
370 _bool_setting(settings, 'gzip_responses', 'false')
369
371
370 # Call split out functions that sanitize settings for each topic.
372 # Call split out functions that sanitize settings for each topic.
371 _sanitize_appenlight_settings(settings)
373 _sanitize_appenlight_settings(settings)
372 _sanitize_vcs_settings(settings)
374 _sanitize_vcs_settings(settings)
373
375
374 # configure instance id
376 # configure instance id
375 config_utils.set_instance_id(settings)
377 config_utils.set_instance_id(settings)
376
378
377 return settings
379 return settings
378
380
379
381
380 def _sanitize_appenlight_settings(settings):
382 def _sanitize_appenlight_settings(settings):
381 _bool_setting(settings, 'appenlight', 'false')
383 _bool_setting(settings, 'appenlight', 'false')
382
384
383
385
384 def _sanitize_vcs_settings(settings):
386 def _sanitize_vcs_settings(settings):
385 """
387 """
386 Applies settings defaults and does type conversion for all VCS related
388 Applies settings defaults and does type conversion for all VCS related
387 settings.
389 settings.
388 """
390 """
389 _string_setting(settings, 'vcs.svn.compatible_version', '')
391 _string_setting(settings, 'vcs.svn.compatible_version', '')
390 _string_setting(settings, 'git_rev_filter', '--all')
392 _string_setting(settings, 'git_rev_filter', '--all')
391 _string_setting(settings, 'vcs.hooks.protocol', 'http')
393 _string_setting(settings, 'vcs.hooks.protocol', 'http')
392 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
394 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
393 _string_setting(settings, 'vcs.server', '')
395 _string_setting(settings, 'vcs.server', '')
394 _string_setting(settings, 'vcs.server.log_level', 'debug')
396 _string_setting(settings, 'vcs.server.log_level', 'debug')
395 _string_setting(settings, 'vcs.server.protocol', 'http')
397 _string_setting(settings, 'vcs.server.protocol', 'http')
396 _bool_setting(settings, 'startup.import_repos', 'false')
398 _bool_setting(settings, 'startup.import_repos', 'false')
397 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
399 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
398 _bool_setting(settings, 'vcs.server.enable', 'true')
400 _bool_setting(settings, 'vcs.server.enable', 'true')
399 _bool_setting(settings, 'vcs.start_server', 'false')
401 _bool_setting(settings, 'vcs.start_server', 'false')
400 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
402 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
401 _int_setting(settings, 'vcs.connection_timeout', 3600)
403 _int_setting(settings, 'vcs.connection_timeout', 3600)
402
404
403 # Support legacy values of vcs.scm_app_implementation. Legacy
405 # Support legacy values of vcs.scm_app_implementation. Legacy
404 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
406 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
405 # which is now mapped to 'http'.
407 # which is now mapped to 'http'.
406 scm_app_impl = settings['vcs.scm_app_implementation']
408 scm_app_impl = settings['vcs.scm_app_implementation']
407 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
409 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
408 settings['vcs.scm_app_implementation'] = 'http'
410 settings['vcs.scm_app_implementation'] = 'http'
409
411
410
412
411 def _int_setting(settings, name, default):
413 def _int_setting(settings, name, default):
412 settings[name] = int(settings.get(name, default))
414 settings[name] = int(settings.get(name, default))
413
415
414
416
415 def _bool_setting(settings, name, default):
417 def _bool_setting(settings, name, default):
416 input_val = settings.get(name, default)
418 input_val = settings.get(name, default)
417 if isinstance(input_val, unicode):
419 if isinstance(input_val, unicode):
418 input_val = input_val.encode('utf8')
420 input_val = input_val.encode('utf8')
419 settings[name] = asbool(input_val)
421 settings[name] = asbool(input_val)
420
422
421
423
422 def _list_setting(settings, name, default):
424 def _list_setting(settings, name, default):
423 raw_value = settings.get(name, default)
425 raw_value = settings.get(name, default)
424
426
425 old_separator = ','
427 old_separator = ','
426 if old_separator in raw_value:
428 if old_separator in raw_value:
427 # If we get a comma separated list, pass it to our own function.
429 # If we get a comma separated list, pass it to our own function.
428 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
430 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
429 else:
431 else:
430 # Otherwise we assume it uses pyramids space/newline separation.
432 # Otherwise we assume it uses pyramids space/newline separation.
431 settings[name] = aslist(raw_value)
433 settings[name] = aslist(raw_value)
432
434
433
435
434 def _string_setting(settings, name, default, lower=True):
436 def _string_setting(settings, name, default, lower=True):
435 value = settings.get(name, default)
437 value = settings.get(name, default)
436 if lower:
438 if lower:
437 value = value.lower()
439 value = value.lower()
438 settings[name] = value
440 settings[name] = value
@@ -1,141 +1,170 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 sys
21 import sys
22 import logging
22 import logging
23
23
24
24
25 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
25 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
26
26
27 # Sequences
27 # Sequences
28 RESET_SEQ = "\033[0m"
28 RESET_SEQ = "\033[0m"
29 COLOR_SEQ = "\033[0;%dm"
29 COLOR_SEQ = "\033[0;%dm"
30 BOLD_SEQ = "\033[1m"
30 BOLD_SEQ = "\033[1m"
31
31
32 COLORS = {
32 COLORS = {
33 'CRITICAL': MAGENTA,
33 'CRITICAL': MAGENTA,
34 'ERROR': RED,
34 'ERROR': RED,
35 'WARNING': CYAN,
35 'WARNING': CYAN,
36 'INFO': GREEN,
36 'INFO': GREEN,
37 'DEBUG': BLUE,
37 'DEBUG': BLUE,
38 'SQL': YELLOW
38 'SQL': YELLOW
39 }
39 }
40
40
41
41
42 def one_space_trim(s):
42 def one_space_trim(s):
43 if s.find(" ") == -1:
43 if s.find(" ") == -1:
44 return s
44 return s
45 else:
45 else:
46 s = s.replace(' ', ' ')
46 s = s.replace(' ', ' ')
47 return one_space_trim(s)
47 return one_space_trim(s)
48
48
49
49
50 def format_sql(sql):
50 def format_sql(sql):
51 sql = sql.replace('\n', '')
51 sql = sql.replace('\n', '')
52 sql = one_space_trim(sql)
52 sql = one_space_trim(sql)
53 sql = sql\
53 sql = sql\
54 .replace(',', ',\n\t')\
54 .replace(',', ',\n\t')\
55 .replace('SELECT', '\n\tSELECT \n\t')\
55 .replace('SELECT', '\n\tSELECT \n\t')\
56 .replace('UPDATE', '\n\tUPDATE \n\t')\
56 .replace('UPDATE', '\n\tUPDATE \n\t')\
57 .replace('DELETE', '\n\tDELETE \n\t')\
57 .replace('DELETE', '\n\tDELETE \n\t')\
58 .replace('FROM', '\n\tFROM')\
58 .replace('FROM', '\n\tFROM')\
59 .replace('ORDER BY', '\n\tORDER BY')\
59 .replace('ORDER BY', '\n\tORDER BY')\
60 .replace('LIMIT', '\n\tLIMIT')\
60 .replace('LIMIT', '\n\tLIMIT')\
61 .replace('WHERE', '\n\tWHERE')\
61 .replace('WHERE', '\n\tWHERE')\
62 .replace('AND', '\n\tAND')\
62 .replace('AND', '\n\tAND')\
63 .replace('LEFT', '\n\tLEFT')\
63 .replace('LEFT', '\n\tLEFT')\
64 .replace('INNER', '\n\tINNER')\
64 .replace('INNER', '\n\tINNER')\
65 .replace('INSERT', '\n\tINSERT')\
65 .replace('INSERT', '\n\tINSERT')\
66 .replace('DELETE', '\n\tDELETE')
66 .replace('DELETE', '\n\tDELETE')
67 return sql
67 return sql
68
68
69
69
70 class ExceptionAwareFormatter(logging.Formatter):
70 class ExceptionAwareFormatter(logging.Formatter):
71 """
71 """
72 Extended logging formatter which prints out remote tracebacks.
72 Extended logging formatter which prints out remote tracebacks.
73 """
73 """
74
74
75 def formatException(self, ei):
75 def formatException(self, ei):
76 ex_type, ex_value, ex_tb = ei
76 ex_type, ex_value, ex_tb = ei
77
77
78 local_tb = logging.Formatter.formatException(self, ei)
78 local_tb = logging.Formatter.formatException(self, ei)
79 if hasattr(ex_value, '_vcs_server_traceback'):
79 if hasattr(ex_value, '_vcs_server_traceback'):
80
80
81 def formatRemoteTraceback(remote_tb_lines):
81 def formatRemoteTraceback(remote_tb_lines):
82 result = ["\n +--- This exception occured remotely on VCSServer - Remote traceback:\n\n"]
82 result = ["\n +--- This exception occured remotely on VCSServer - Remote traceback:\n\n"]
83 result.append(remote_tb_lines)
83 result.append(remote_tb_lines)
84 result.append("\n +--- End of remote traceback\n")
84 result.append("\n +--- End of remote traceback\n")
85 return result
85 return result
86
86
87 try:
87 try:
88 if ex_type is not None and ex_value is None and ex_tb is None:
88 if ex_type is not None and ex_value is None and ex_tb is None:
89 # possible old (3.x) call syntax where caller is only
89 # possible old (3.x) call syntax where caller is only
90 # providing exception object
90 # providing exception object
91 if type(ex_type) is not type:
91 if type(ex_type) is not type:
92 raise TypeError(
92 raise TypeError(
93 "invalid argument: ex_type should be an exception "
93 "invalid argument: ex_type should be an exception "
94 "type, or just supply no arguments at all")
94 "type, or just supply no arguments at all")
95 if ex_type is None and ex_tb is None:
95 if ex_type is None and ex_tb is None:
96 ex_type, ex_value, ex_tb = sys.exc_info()
96 ex_type, ex_value, ex_tb = sys.exc_info()
97
97
98 remote_tb = getattr(ex_value, "_vcs_server_traceback", None)
98 remote_tb = getattr(ex_value, "_vcs_server_traceback", None)
99
99
100 if remote_tb:
100 if remote_tb:
101 remote_tb = formatRemoteTraceback(remote_tb)
101 remote_tb = formatRemoteTraceback(remote_tb)
102 return local_tb + ''.join(remote_tb)
102 return local_tb + ''.join(remote_tb)
103 finally:
103 finally:
104 # clean up cycle to traceback, to allow proper GC
104 # clean up cycle to traceback, to allow proper GC
105 del ex_type, ex_value, ex_tb
105 del ex_type, ex_value, ex_tb
106
106
107 return local_tb
107 return local_tb
108
108
109
109
110 class ColorFormatter(ExceptionAwareFormatter):
110 class ColorFormatter(ExceptionAwareFormatter):
111
111
112 def format(self, record):
112 def format(self, record):
113 """
113 """
114 Changes record's levelname to use with COLORS enum
114 Changes record's levelname to use with COLORS enum
115 """
115 """
116
116
117 levelname = record.levelname
117 levelname = record.levelname
118 start = COLOR_SEQ % (COLORS[levelname])
118 start = COLOR_SEQ % (COLORS[levelname])
119 def_record = logging.Formatter.format(self, record)
119 def_record = logging.Formatter.format(self, record)
120 end = RESET_SEQ
120 end = RESET_SEQ
121
121
122 colored_record = ''.join([start, def_record, end])
122 colored_record = ''.join([start, def_record, end])
123 return colored_record
123 return colored_record
124
124
125
125
126 def _inject_req_id(record):
127 from pyramid.threadlocal import get_current_request
128 req = get_current_request()
129 req_id = 'req_id:%-36s ' % (getattr(req, 'req_id', None))
130 record.req_id = req_id
131
132
133 class RequestTrackingFormatter(ExceptionAwareFormatter):
134 def format(self, record):
135 _inject_req_id(record)
136 def_record = logging.Formatter.format(self, record)
137 return def_record
138
139
140 class ColorRequestTrackingFormatter(ColorFormatter):
141 def format(self, record):
142 """
143 Changes record's levelname to use with COLORS enum
144 """
145 _inject_req_id(record)
146 levelname = record.levelname
147 start = COLOR_SEQ % (COLORS[levelname])
148 def_record = logging.Formatter.format(self, record)
149 end = RESET_SEQ
150
151 colored_record = ''.join([start, def_record, end])
152 return colored_record
153
154
126 class ColorFormatterSql(logging.Formatter):
155 class ColorFormatterSql(logging.Formatter):
127
156
128 def format(self, record):
157 def format(self, record):
129 """
158 """
130 Changes record's levelname to use with COLORS enum
159 Changes record's levelname to use with COLORS enum
131 """
160 """
132
161
133 start = COLOR_SEQ % (COLORS['SQL'])
162 start = COLOR_SEQ % (COLORS['SQL'])
134 def_record = format_sql(logging.Formatter.format(self, record))
163 def_record = format_sql(logging.Formatter.format(self, record))
135 end = RESET_SEQ
164 end = RESET_SEQ
136
165
137 colored_record = ''.join([start, def_record, end])
166 colored_record = ''.join([start, def_record, end])
138 return colored_record
167 return colored_record
139
168
140 # marcink: needs to stay with this name for backward .ini compatability
169 # marcink: needs to stay with this name for backward .ini compatability
141 Pyro4AwareFormatter = ExceptionAwareFormatter
170 Pyro4AwareFormatter = ExceptionAwareFormatter
@@ -1,244 +1,244 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 gzip
21 import gzip
22 import shutil
22 import shutil
23 import logging
23 import logging
24 import tempfile
24 import tempfile
25 import urlparse
25 import urlparse
26
26
27 from webob.exc import HTTPNotFound
27 from webob.exc import HTTPNotFound
28
28
29 import rhodecode
29 import rhodecode
30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
32 from rhodecode.lib.middleware.simplehg import SimpleHg
32 from rhodecode.lib.middleware.simplehg import SimpleHg
33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
34 from rhodecode.model.settings import VcsSettingsModel
34 from rhodecode.model.settings import VcsSettingsModel
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38 VCS_TYPE_KEY = '_rc_vcs_type'
38 VCS_TYPE_KEY = '_rc_vcs_type'
39 VCS_TYPE_SKIP = '_rc_vcs_skip'
39 VCS_TYPE_SKIP = '_rc_vcs_skip'
40
40
41
41
42 def is_git(environ):
42 def is_git(environ):
43 """
43 """
44 Returns True if requests should be handled by GIT wsgi middleware
44 Returns True if requests should be handled by GIT wsgi middleware
45 """
45 """
46 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
46 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
47 log.debug(
47 log.debug(
48 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
48 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
49 is_git_path is not None)
49 is_git_path is not None)
50
50
51 return is_git_path
51 return is_git_path
52
52
53
53
54 def is_hg(environ):
54 def is_hg(environ):
55 """
55 """
56 Returns True if requests target is mercurial server - header
56 Returns True if requests target is mercurial server - header
57 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
57 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
58 """
58 """
59 is_hg_path = False
59 is_hg_path = False
60
60
61 http_accept = environ.get('HTTP_ACCEPT')
61 http_accept = environ.get('HTTP_ACCEPT')
62
62
63 if http_accept and http_accept.startswith('application/mercurial'):
63 if http_accept and http_accept.startswith('application/mercurial'):
64 query = urlparse.parse_qs(environ['QUERY_STRING'])
64 query = urlparse.parse_qs(environ['QUERY_STRING'])
65 if 'cmd' in query:
65 if 'cmd' in query:
66 is_hg_path = True
66 is_hg_path = True
67
67
68 log.debug(
68 log.debug(
69 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
69 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
70 is_hg_path)
70 is_hg_path)
71
71
72 return is_hg_path
72 return is_hg_path
73
73
74
74
75 def is_svn(environ):
75 def is_svn(environ):
76 """
76 """
77 Returns True if requests target is Subversion server
77 Returns True if requests target is Subversion server
78 """
78 """
79
79
80 http_dav = environ.get('HTTP_DAV', '')
80 http_dav = environ.get('HTTP_DAV', '')
81 magic_path_segment = rhodecode.CONFIG.get(
81 magic_path_segment = rhodecode.CONFIG.get(
82 'rhodecode_subversion_magic_path', '/!svn')
82 'rhodecode_subversion_magic_path', '/!svn')
83 is_svn_path = (
83 is_svn_path = (
84 'subversion' in http_dav or
84 'subversion' in http_dav or
85 magic_path_segment in environ['PATH_INFO']
85 magic_path_segment in environ['PATH_INFO']
86 or environ['REQUEST_METHOD'] in ['PROPFIND', 'PROPPATCH']
86 or environ['REQUEST_METHOD'] in ['PROPFIND', 'PROPPATCH']
87 )
87 )
88 log.debug(
88 log.debug(
89 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
89 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
90 is_svn_path)
90 is_svn_path)
91
91
92 return is_svn_path
92 return is_svn_path
93
93
94
94
95 class GunzipMiddleware(object):
95 class GunzipMiddleware(object):
96 """
96 """
97 WSGI middleware that unzips gzip-encoded requests before
97 WSGI middleware that unzips gzip-encoded requests before
98 passing on to the underlying application.
98 passing on to the underlying application.
99 """
99 """
100
100
101 def __init__(self, application):
101 def __init__(self, application):
102 self.app = application
102 self.app = application
103
103
104 def __call__(self, environ, start_response):
104 def __call__(self, environ, start_response):
105 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
105 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
106
106
107 if b'gzip' in accepts_encoding_header:
107 if b'gzip' in accepts_encoding_header:
108 log.debug('gzip detected, now running gunzip wrapper')
108 log.debug('gzip detected, now running gunzip wrapper')
109 wsgi_input = environ['wsgi.input']
109 wsgi_input = environ['wsgi.input']
110
110
111 if not hasattr(environ['wsgi.input'], 'seek'):
111 if not hasattr(environ['wsgi.input'], 'seek'):
112 # The gzip implementation in the standard library of Python 2.x
112 # The gzip implementation in the standard library of Python 2.x
113 # requires the '.seek()' and '.tell()' methods to be available
113 # requires the '.seek()' and '.tell()' methods to be available
114 # on the input stream. Read the data into a temporary file to
114 # on the input stream. Read the data into a temporary file to
115 # work around this limitation.
115 # work around this limitation.
116
116
117 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
117 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
118 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
118 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
119 wsgi_input.seek(0)
119 wsgi_input.seek(0)
120
120
121 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
121 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
122 # since we "Ungzipped" the content we say now it's no longer gzip
122 # since we "Ungzipped" the content we say now it's no longer gzip
123 # content encoding
123 # content encoding
124 del environ['HTTP_CONTENT_ENCODING']
124 del environ['HTTP_CONTENT_ENCODING']
125
125
126 # content length has changes ? or i'm not sure
126 # content length has changes ? or i'm not sure
127 if 'CONTENT_LENGTH' in environ:
127 if 'CONTENT_LENGTH' in environ:
128 del environ['CONTENT_LENGTH']
128 del environ['CONTENT_LENGTH']
129 else:
129 else:
130 log.debug('content not gzipped, gzipMiddleware passing '
130 log.debug('content not gzipped, gzipMiddleware passing '
131 'request further')
131 'request further')
132 return self.app(environ, start_response)
132 return self.app(environ, start_response)
133
133
134
134
135 def is_vcs_call(environ):
135 def is_vcs_call(environ):
136 if VCS_TYPE_KEY in environ:
136 if VCS_TYPE_KEY in environ:
137 raw_type = environ[VCS_TYPE_KEY]
137 raw_type = environ[VCS_TYPE_KEY]
138 return raw_type and raw_type != VCS_TYPE_SKIP
138 return raw_type and raw_type != VCS_TYPE_SKIP
139 return False
139 return False
140
140
141
141
142 def detect_vcs_request(environ, backends):
142 def detect_vcs_request(environ, backends):
143 checks = {
143 checks = {
144 'hg': (is_hg, SimpleHg),
144 'hg': (is_hg, SimpleHg),
145 'git': (is_git, SimpleGit),
145 'git': (is_git, SimpleGit),
146 'svn': (is_svn, SimpleSvn),
146 'svn': (is_svn, SimpleSvn),
147 }
147 }
148 handler = None
148 handler = None
149
149
150 if VCS_TYPE_KEY in environ:
150 if VCS_TYPE_KEY in environ:
151 raw_type = environ[VCS_TYPE_KEY]
151 raw_type = environ[VCS_TYPE_KEY]
152 if raw_type == VCS_TYPE_SKIP:
152 if raw_type == VCS_TYPE_SKIP:
153 log.debug('got `skip` marker for vcs detection, skipping...')
153 log.debug('got `skip` marker for vcs detection, skipping...')
154 return handler
154 return handler
155
155
156 _check, handler = checks.get(raw_type) or [None, None]
156 _check, handler = checks.get(raw_type) or [None, None]
157 if handler:
157 if handler:
158 log.debug('got handler:%s from environ', handler)
158 log.debug('got handler:%s from environ', handler)
159
159
160 if not handler:
160 if not handler:
161 log.debug('checking if request is of VCS type in order: %s', backends)
161 log.debug('request start: checking if request is of VCS type in order: %s', backends)
162 for vcs_type in backends:
162 for vcs_type in backends:
163 vcs_check, _handler = checks[vcs_type]
163 vcs_check, _handler = checks[vcs_type]
164 if vcs_check(environ):
164 if vcs_check(environ):
165 log.debug('vcs handler found %s', _handler)
165 log.debug('vcs handler found %s', _handler)
166 handler = _handler
166 handler = _handler
167 break
167 break
168
168
169 return handler
169 return handler
170
170
171
171
172 class VCSMiddleware(object):
172 class VCSMiddleware(object):
173
173
174 def __init__(self, app, registry, config, appenlight_client):
174 def __init__(self, app, registry, config, appenlight_client):
175 self.application = app
175 self.application = app
176 self.registry = registry
176 self.registry = registry
177 self.config = config
177 self.config = config
178 self.appenlight_client = appenlight_client
178 self.appenlight_client = appenlight_client
179 self.use_gzip = True
179 self.use_gzip = True
180 # order in which we check the middlewares, based on vcs.backends config
180 # order in which we check the middlewares, based on vcs.backends config
181 self.check_middlewares = config['vcs.backends']
181 self.check_middlewares = config['vcs.backends']
182
182
183 def vcs_config(self, repo_name=None):
183 def vcs_config(self, repo_name=None):
184 """
184 """
185 returns serialized VcsSettings
185 returns serialized VcsSettings
186 """
186 """
187 try:
187 try:
188 return VcsSettingsModel(
188 return VcsSettingsModel(
189 repo=repo_name).get_ui_settings_as_config_obj()
189 repo=repo_name).get_ui_settings_as_config_obj()
190 except Exception:
190 except Exception:
191 pass
191 pass
192
192
193 def wrap_in_gzip_if_enabled(self, app, config):
193 def wrap_in_gzip_if_enabled(self, app, config):
194 if self.use_gzip:
194 if self.use_gzip:
195 app = GunzipMiddleware(app)
195 app = GunzipMiddleware(app)
196 return app
196 return app
197
197
198 def _get_handler_app(self, environ):
198 def _get_handler_app(self, environ):
199 app = None
199 app = None
200 log.debug('VCSMiddleware: detecting vcs type.')
200 log.debug('VCSMiddleware: detecting vcs type.')
201 handler = detect_vcs_request(environ, self.check_middlewares)
201 handler = detect_vcs_request(environ, self.check_middlewares)
202 if handler:
202 if handler:
203 app = handler(self.config, self.registry)
203 app = handler(self.config, self.registry)
204
204
205 return app
205 return app
206
206
207 def __call__(self, environ, start_response):
207 def __call__(self, environ, start_response):
208 # check if we handle one of interesting protocols, optionally extract
208 # check if we handle one of interesting protocols, optionally extract
209 # specific vcsSettings and allow changes of how things are wrapped
209 # specific vcsSettings and allow changes of how things are wrapped
210 vcs_handler = self._get_handler_app(environ)
210 vcs_handler = self._get_handler_app(environ)
211 if vcs_handler:
211 if vcs_handler:
212 # translate the _REPO_ID into real repo NAME for usage
212 # translate the _REPO_ID into real repo NAME for usage
213 # in middleware
213 # in middleware
214 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
214 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
215
215
216 # Set acl, url and vcs repo names.
216 # Set acl, url and vcs repo names.
217 vcs_handler.set_repo_names(environ)
217 vcs_handler.set_repo_names(environ)
218
218
219 # register repo config back to the handler
219 # register repo config back to the handler
220 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
220 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
221 # maybe damaged/non existent settings. We still want to
221 # maybe damaged/non existent settings. We still want to
222 # pass that point to validate on is_valid_and_existing_repo
222 # pass that point to validate on is_valid_and_existing_repo
223 # and return proper HTTP Code back to client
223 # and return proper HTTP Code back to client
224 if vcs_conf:
224 if vcs_conf:
225 vcs_handler.repo_vcs_config = vcs_conf
225 vcs_handler.repo_vcs_config = vcs_conf
226
226
227 # check for type, presence in database and on filesystem
227 # check for type, presence in database and on filesystem
228 if not vcs_handler.is_valid_and_existing_repo(
228 if not vcs_handler.is_valid_and_existing_repo(
229 vcs_handler.acl_repo_name,
229 vcs_handler.acl_repo_name,
230 vcs_handler.base_path,
230 vcs_handler.base_path,
231 vcs_handler.SCM):
231 vcs_handler.SCM):
232 return HTTPNotFound()(environ, start_response)
232 return HTTPNotFound()(environ, start_response)
233
233
234 environ['REPO_NAME'] = vcs_handler.url_repo_name
234 environ['REPO_NAME'] = vcs_handler.url_repo_name
235
235
236 # Wrap handler in middlewares if they are enabled.
236 # Wrap handler in middlewares if they are enabled.
237 vcs_handler = self.wrap_in_gzip_if_enabled(
237 vcs_handler = self.wrap_in_gzip_if_enabled(
238 vcs_handler, self.config)
238 vcs_handler, self.config)
239 vcs_handler, _ = wrap_in_appenlight_if_enabled(
239 vcs_handler, _ = wrap_in_appenlight_if_enabled(
240 vcs_handler, self.config, self.appenlight_client)
240 vcs_handler, self.config, self.appenlight_client)
241
241
242 return vcs_handler(environ, start_response)
242 return vcs_handler(environ, start_response)
243
243
244 return self.application(environ, start_response)
244 return self.application(environ, start_response)
@@ -1,58 +1,59 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 from pyramid.compat import configparser
22 from pyramid.compat import configparser
23 from pyramid.paster import bootstrap as pyramid_bootstrap, setup_logging # noqa
23 from pyramid.paster import bootstrap as pyramid_bootstrap, setup_logging # noqa
24 from pyramid.request import Request
25 from pyramid.scripting import prepare
24 from pyramid.scripting import prepare
26
25
26 from rhodecode.lib.request import Request
27
27
28
28 def get_config(ini_path, **kwargs):
29 def get_config(ini_path, **kwargs):
29 parser = configparser.ConfigParser(**kwargs)
30 parser = configparser.ConfigParser(**kwargs)
30 parser.read(ini_path)
31 parser.read(ini_path)
31 return parser
32 return parser
32
33
33
34
34 def get_app_config(ini_path):
35 def get_app_config(ini_path):
35 from paste.deploy.loadwsgi import appconfig
36 from paste.deploy.loadwsgi import appconfig
36 return appconfig('config:{}'.format(ini_path), relative_to=os.getcwd())
37 return appconfig('config:{}'.format(ini_path), relative_to=os.getcwd())
37
38
38
39
39 def bootstrap(config_uri, request=None, options=None, env=None):
40 def bootstrap(config_uri, request=None, options=None, env=None):
40 if env:
41 if env:
41 os.environ.update(env)
42 os.environ.update(env)
42
43
43 config = get_config(config_uri)
44 config = get_config(config_uri)
44 base_url = 'http://rhodecode.local'
45 base_url = 'http://rhodecode.local'
45 try:
46 try:
46 base_url = config.get('app:main', 'app.base_url')
47 base_url = config.get('app:main', 'app.base_url')
47 except (configparser.NoSectionError, configparser.NoOptionError):
48 except (configparser.NoSectionError, configparser.NoOptionError):
48 pass
49 pass
49
50
50 request = request or Request.blank('/', base_url=base_url)
51 request = request or Request.blank('/', base_url=base_url)
51
52
52 return pyramid_bootstrap(config_uri, request=request, options=options)
53 return pyramid_bootstrap(config_uri, request=request, options=options)
53
54
54
55
55 def prepare_request(environ):
56 def prepare_request(environ):
56 request = Request.blank('/', environ=environ)
57 request = Request.blank('/', environ=environ)
57 prepare(request) # set pyramid threadlocal request
58 prepare(request) # set pyramid threadlocal request
58 return request
59 return request
@@ -1,779 +1,779 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 Utilities library for RhodeCode
22 Utilities library for RhodeCode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import decorator
26 import decorator
27 import json
27 import json
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import shutil
31 import shutil
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35 import warnings
35 import warnings
36 import hashlib
36 import hashlib
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39 import paste
39 import paste
40 import pkg_resources
40 import pkg_resources
41 from webhelpers.text import collapse, remove_formatting, strip_tags
41 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from mako import exceptions
42 from mako import exceptions
43 from pyramid.threadlocal import get_current_registry
43 from pyramid.threadlocal import get_current_registry
44 from pyramid.request import Request
44 from rhodecode.lib.request import Request
45
45
46 from rhodecode.lib.fakemod import create_module
46 from rhodecode.lib.fakemod import create_module
47 from rhodecode.lib.vcs.backends.base import Config
47 from rhodecode.lib.vcs.backends.base import Config
48 from rhodecode.lib.vcs.exceptions import VCSError
48 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
49 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 from rhodecode.lib.utils2 import (
50 from rhodecode.lib.utils2 import (
51 safe_str, safe_unicode, get_current_rhodecode_user, md5)
51 safe_str, safe_unicode, get_current_rhodecode_user, md5)
52 from rhodecode.model import meta
52 from rhodecode.model import meta
53 from rhodecode.model.db import (
53 from rhodecode.model.db import (
54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 from rhodecode.model.meta import Session
55 from rhodecode.model.meta import Session
56
56
57
57
58 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
59
59
60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61
61
62 # String which contains characters that are not allowed in slug names for
62 # String which contains characters that are not allowed in slug names for
63 # repositories or repository groups. It is properly escaped to use it in
63 # repositories or repository groups. It is properly escaped to use it in
64 # regular expressions.
64 # regular expressions.
65 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
65 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66
66
67 # Regex that matches forbidden characters in repo/group slugs.
67 # Regex that matches forbidden characters in repo/group slugs.
68 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
68 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69
69
70 # Regex that matches allowed characters in repo/group slugs.
70 # Regex that matches allowed characters in repo/group slugs.
71 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
71 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72
72
73 # Regex that matches whole repo/group slugs.
73 # Regex that matches whole repo/group slugs.
74 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
74 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75
75
76 _license_cache = None
76 _license_cache = None
77
77
78
78
79 def repo_name_slug(value):
79 def repo_name_slug(value):
80 """
80 """
81 Return slug of name of repository
81 Return slug of name of repository
82 This function is called on each creation/modification
82 This function is called on each creation/modification
83 of repository to prevent bad names in repo
83 of repository to prevent bad names in repo
84 """
84 """
85 replacement_char = '-'
85 replacement_char = '-'
86
86
87 slug = remove_formatting(value)
87 slug = remove_formatting(value)
88 slug = SLUG_BAD_CHAR_RE.sub('', slug)
88 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 slug = re.sub('[\s]+', '-', slug)
89 slug = re.sub('[\s]+', '-', slug)
90 slug = collapse(slug, replacement_char)
90 slug = collapse(slug, replacement_char)
91 return slug
91 return slug
92
92
93
93
94 #==============================================================================
94 #==============================================================================
95 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
95 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 #==============================================================================
96 #==============================================================================
97 def get_repo_slug(request):
97 def get_repo_slug(request):
98 _repo = ''
98 _repo = ''
99
99
100 if hasattr(request, 'db_repo'):
100 if hasattr(request, 'db_repo'):
101 # if our requests has set db reference use it for name, this
101 # if our requests has set db reference use it for name, this
102 # translates the example.com/_<id> into proper repo names
102 # translates the example.com/_<id> into proper repo names
103 _repo = request.db_repo.repo_name
103 _repo = request.db_repo.repo_name
104 elif getattr(request, 'matchdict', None):
104 elif getattr(request, 'matchdict', None):
105 # pyramid
105 # pyramid
106 _repo = request.matchdict.get('repo_name')
106 _repo = request.matchdict.get('repo_name')
107
107
108 if _repo:
108 if _repo:
109 _repo = _repo.rstrip('/')
109 _repo = _repo.rstrip('/')
110 return _repo
110 return _repo
111
111
112
112
113 def get_repo_group_slug(request):
113 def get_repo_group_slug(request):
114 _group = ''
114 _group = ''
115 if hasattr(request, 'db_repo_group'):
115 if hasattr(request, 'db_repo_group'):
116 # if our requests has set db reference use it for name, this
116 # if our requests has set db reference use it for name, this
117 # translates the example.com/_<id> into proper repo group names
117 # translates the example.com/_<id> into proper repo group names
118 _group = request.db_repo_group.group_name
118 _group = request.db_repo_group.group_name
119 elif getattr(request, 'matchdict', None):
119 elif getattr(request, 'matchdict', None):
120 # pyramid
120 # pyramid
121 _group = request.matchdict.get('repo_group_name')
121 _group = request.matchdict.get('repo_group_name')
122
122
123
123
124 if _group:
124 if _group:
125 _group = _group.rstrip('/')
125 _group = _group.rstrip('/')
126 return _group
126 return _group
127
127
128
128
129 def get_user_group_slug(request):
129 def get_user_group_slug(request):
130 _user_group = ''
130 _user_group = ''
131
131
132 if hasattr(request, 'db_user_group'):
132 if hasattr(request, 'db_user_group'):
133 _user_group = request.db_user_group.users_group_name
133 _user_group = request.db_user_group.users_group_name
134 elif getattr(request, 'matchdict', None):
134 elif getattr(request, 'matchdict', None):
135 # pyramid
135 # pyramid
136 _user_group = request.matchdict.get('user_group_id')
136 _user_group = request.matchdict.get('user_group_id')
137 _user_group_name = request.matchdict.get('user_group_name')
137 _user_group_name = request.matchdict.get('user_group_name')
138 try:
138 try:
139 if _user_group:
139 if _user_group:
140 _user_group = UserGroup.get(_user_group)
140 _user_group = UserGroup.get(_user_group)
141 elif _user_group_name:
141 elif _user_group_name:
142 _user_group = UserGroup.get_by_group_name(_user_group_name)
142 _user_group = UserGroup.get_by_group_name(_user_group_name)
143
143
144 if _user_group:
144 if _user_group:
145 _user_group = _user_group.users_group_name
145 _user_group = _user_group.users_group_name
146 except Exception:
146 except Exception:
147 log.exception('Failed to get user group by id and name')
147 log.exception('Failed to get user group by id and name')
148 # catch all failures here
148 # catch all failures here
149 return None
149 return None
150
150
151 return _user_group
151 return _user_group
152
152
153
153
154 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
154 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
155 """
155 """
156 Scans given path for repos and return (name,(type,path)) tuple
156 Scans given path for repos and return (name,(type,path)) tuple
157
157
158 :param path: path to scan for repositories
158 :param path: path to scan for repositories
159 :param recursive: recursive search and return names with subdirs in front
159 :param recursive: recursive search and return names with subdirs in front
160 """
160 """
161
161
162 # remove ending slash for better results
162 # remove ending slash for better results
163 path = path.rstrip(os.sep)
163 path = path.rstrip(os.sep)
164 log.debug('now scanning in %s location recursive:%s...', path, recursive)
164 log.debug('now scanning in %s location recursive:%s...', path, recursive)
165
165
166 def _get_repos(p):
166 def _get_repos(p):
167 dirpaths = _get_dirpaths(p)
167 dirpaths = _get_dirpaths(p)
168 if not _is_dir_writable(p):
168 if not _is_dir_writable(p):
169 log.warning('repo path without write access: %s', p)
169 log.warning('repo path without write access: %s', p)
170
170
171 for dirpath in dirpaths:
171 for dirpath in dirpaths:
172 if os.path.isfile(os.path.join(p, dirpath)):
172 if os.path.isfile(os.path.join(p, dirpath)):
173 continue
173 continue
174 cur_path = os.path.join(p, dirpath)
174 cur_path = os.path.join(p, dirpath)
175
175
176 # skip removed repos
176 # skip removed repos
177 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
177 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
178 continue
178 continue
179
179
180 #skip .<somethin> dirs
180 #skip .<somethin> dirs
181 if dirpath.startswith('.'):
181 if dirpath.startswith('.'):
182 continue
182 continue
183
183
184 try:
184 try:
185 scm_info = get_scm(cur_path)
185 scm_info = get_scm(cur_path)
186 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
186 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
187 except VCSError:
187 except VCSError:
188 if not recursive:
188 if not recursive:
189 continue
189 continue
190 #check if this dir containts other repos for recursive scan
190 #check if this dir containts other repos for recursive scan
191 rec_path = os.path.join(p, dirpath)
191 rec_path = os.path.join(p, dirpath)
192 if os.path.isdir(rec_path):
192 if os.path.isdir(rec_path):
193 for inner_scm in _get_repos(rec_path):
193 for inner_scm in _get_repos(rec_path):
194 yield inner_scm
194 yield inner_scm
195
195
196 return _get_repos(path)
196 return _get_repos(path)
197
197
198
198
199 def _get_dirpaths(p):
199 def _get_dirpaths(p):
200 try:
200 try:
201 # OS-independable way of checking if we have at least read-only
201 # OS-independable way of checking if we have at least read-only
202 # access or not.
202 # access or not.
203 dirpaths = os.listdir(p)
203 dirpaths = os.listdir(p)
204 except OSError:
204 except OSError:
205 log.warning('ignoring repo path without read access: %s', p)
205 log.warning('ignoring repo path without read access: %s', p)
206 return []
206 return []
207
207
208 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
208 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
209 # decode paths and suddenly returns unicode objects itself. The items it
209 # decode paths and suddenly returns unicode objects itself. The items it
210 # cannot decode are returned as strings and cause issues.
210 # cannot decode are returned as strings and cause issues.
211 #
211 #
212 # Those paths are ignored here until a solid solution for path handling has
212 # Those paths are ignored here until a solid solution for path handling has
213 # been built.
213 # been built.
214 expected_type = type(p)
214 expected_type = type(p)
215
215
216 def _has_correct_type(item):
216 def _has_correct_type(item):
217 if type(item) is not expected_type:
217 if type(item) is not expected_type:
218 log.error(
218 log.error(
219 u"Ignoring path %s since it cannot be decoded into unicode.",
219 u"Ignoring path %s since it cannot be decoded into unicode.",
220 # Using "repr" to make sure that we see the byte value in case
220 # Using "repr" to make sure that we see the byte value in case
221 # of support.
221 # of support.
222 repr(item))
222 repr(item))
223 return False
223 return False
224 return True
224 return True
225
225
226 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
226 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
227
227
228 return dirpaths
228 return dirpaths
229
229
230
230
231 def _is_dir_writable(path):
231 def _is_dir_writable(path):
232 """
232 """
233 Probe if `path` is writable.
233 Probe if `path` is writable.
234
234
235 Due to trouble on Cygwin / Windows, this is actually probing if it is
235 Due to trouble on Cygwin / Windows, this is actually probing if it is
236 possible to create a file inside of `path`, stat does not produce reliable
236 possible to create a file inside of `path`, stat does not produce reliable
237 results in this case.
237 results in this case.
238 """
238 """
239 try:
239 try:
240 with tempfile.TemporaryFile(dir=path):
240 with tempfile.TemporaryFile(dir=path):
241 pass
241 pass
242 except OSError:
242 except OSError:
243 return False
243 return False
244 return True
244 return True
245
245
246
246
247 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
247 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
248 """
248 """
249 Returns True if given path is a valid repository False otherwise.
249 Returns True if given path is a valid repository False otherwise.
250 If expect_scm param is given also, compare if given scm is the same
250 If expect_scm param is given also, compare if given scm is the same
251 as expected from scm parameter. If explicit_scm is given don't try to
251 as expected from scm parameter. If explicit_scm is given don't try to
252 detect the scm, just use the given one to check if repo is valid
252 detect the scm, just use the given one to check if repo is valid
253
253
254 :param repo_name:
254 :param repo_name:
255 :param base_path:
255 :param base_path:
256 :param expect_scm:
256 :param expect_scm:
257 :param explicit_scm:
257 :param explicit_scm:
258 :param config:
258 :param config:
259
259
260 :return True: if given path is a valid repository
260 :return True: if given path is a valid repository
261 """
261 """
262 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
262 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
263 log.debug('Checking if `%s` is a valid path for repository. '
263 log.debug('Checking if `%s` is a valid path for repository. '
264 'Explicit type: %s', repo_name, explicit_scm)
264 'Explicit type: %s', repo_name, explicit_scm)
265
265
266 try:
266 try:
267 if explicit_scm:
267 if explicit_scm:
268 detected_scms = [get_scm_backend(explicit_scm)(
268 detected_scms = [get_scm_backend(explicit_scm)(
269 full_path, config=config).alias]
269 full_path, config=config).alias]
270 else:
270 else:
271 detected_scms = get_scm(full_path)
271 detected_scms = get_scm(full_path)
272
272
273 if expect_scm:
273 if expect_scm:
274 return detected_scms[0] == expect_scm
274 return detected_scms[0] == expect_scm
275 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
275 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
276 return True
276 return True
277 except VCSError:
277 except VCSError:
278 log.debug('path: %s is not a valid repo !', full_path)
278 log.debug('path: %s is not a valid repo !', full_path)
279 return False
279 return False
280
280
281
281
282 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
282 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
283 """
283 """
284 Returns True if given path is a repository group, False otherwise
284 Returns True if given path is a repository group, False otherwise
285
285
286 :param repo_name:
286 :param repo_name:
287 :param base_path:
287 :param base_path:
288 """
288 """
289 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
289 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
290 log.debug('Checking if `%s` is a valid path for repository group',
290 log.debug('Checking if `%s` is a valid path for repository group',
291 repo_group_name)
291 repo_group_name)
292
292
293 # check if it's not a repo
293 # check if it's not a repo
294 if is_valid_repo(repo_group_name, base_path):
294 if is_valid_repo(repo_group_name, base_path):
295 log.debug('Repo called %s exist, it is not a valid '
295 log.debug('Repo called %s exist, it is not a valid '
296 'repo group' % repo_group_name)
296 'repo group' % repo_group_name)
297 return False
297 return False
298
298
299 try:
299 try:
300 # we need to check bare git repos at higher level
300 # we need to check bare git repos at higher level
301 # since we might match branches/hooks/info/objects or possible
301 # since we might match branches/hooks/info/objects or possible
302 # other things inside bare git repo
302 # other things inside bare git repo
303 scm_ = get_scm(os.path.dirname(full_path))
303 scm_ = get_scm(os.path.dirname(full_path))
304 log.debug('path: %s is a vcs object:%s, not valid '
304 log.debug('path: %s is a vcs object:%s, not valid '
305 'repo group' % (full_path, scm_))
305 'repo group' % (full_path, scm_))
306 return False
306 return False
307 except VCSError:
307 except VCSError:
308 pass
308 pass
309
309
310 # check if it's a valid path
310 # check if it's a valid path
311 if skip_path_check or os.path.isdir(full_path):
311 if skip_path_check or os.path.isdir(full_path):
312 log.debug('path: %s is a valid repo group !', full_path)
312 log.debug('path: %s is a valid repo group !', full_path)
313 return True
313 return True
314
314
315 log.debug('path: %s is not a valid repo group !', full_path)
315 log.debug('path: %s is not a valid repo group !', full_path)
316 return False
316 return False
317
317
318
318
319 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
319 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
320 while True:
320 while True:
321 ok = raw_input(prompt)
321 ok = raw_input(prompt)
322 if ok.lower() in ('y', 'ye', 'yes'):
322 if ok.lower() in ('y', 'ye', 'yes'):
323 return True
323 return True
324 if ok.lower() in ('n', 'no', 'nop', 'nope'):
324 if ok.lower() in ('n', 'no', 'nop', 'nope'):
325 return False
325 return False
326 retries = retries - 1
326 retries = retries - 1
327 if retries < 0:
327 if retries < 0:
328 raise IOError
328 raise IOError
329 print(complaint)
329 print(complaint)
330
330
331 # propagated from mercurial documentation
331 # propagated from mercurial documentation
332 ui_sections = [
332 ui_sections = [
333 'alias', 'auth',
333 'alias', 'auth',
334 'decode/encode', 'defaults',
334 'decode/encode', 'defaults',
335 'diff', 'email',
335 'diff', 'email',
336 'extensions', 'format',
336 'extensions', 'format',
337 'merge-patterns', 'merge-tools',
337 'merge-patterns', 'merge-tools',
338 'hooks', 'http_proxy',
338 'hooks', 'http_proxy',
339 'smtp', 'patch',
339 'smtp', 'patch',
340 'paths', 'profiling',
340 'paths', 'profiling',
341 'server', 'trusted',
341 'server', 'trusted',
342 'ui', 'web', ]
342 'ui', 'web', ]
343
343
344
344
345 def config_data_from_db(clear_session=True, repo=None):
345 def config_data_from_db(clear_session=True, repo=None):
346 """
346 """
347 Read the configuration data from the database and return configuration
347 Read the configuration data from the database and return configuration
348 tuples.
348 tuples.
349 """
349 """
350 from rhodecode.model.settings import VcsSettingsModel
350 from rhodecode.model.settings import VcsSettingsModel
351
351
352 config = []
352 config = []
353
353
354 sa = meta.Session()
354 sa = meta.Session()
355 settings_model = VcsSettingsModel(repo=repo, sa=sa)
355 settings_model = VcsSettingsModel(repo=repo, sa=sa)
356
356
357 ui_settings = settings_model.get_ui_settings()
357 ui_settings = settings_model.get_ui_settings()
358
358
359 ui_data = []
359 ui_data = []
360 for setting in ui_settings:
360 for setting in ui_settings:
361 if setting.active:
361 if setting.active:
362 ui_data.append((setting.section, setting.key, setting.value))
362 ui_data.append((setting.section, setting.key, setting.value))
363 config.append((
363 config.append((
364 safe_str(setting.section), safe_str(setting.key),
364 safe_str(setting.section), safe_str(setting.key),
365 safe_str(setting.value)))
365 safe_str(setting.value)))
366 if setting.key == 'push_ssl':
366 if setting.key == 'push_ssl':
367 # force set push_ssl requirement to False, rhodecode
367 # force set push_ssl requirement to False, rhodecode
368 # handles that
368 # handles that
369 config.append((
369 config.append((
370 safe_str(setting.section), safe_str(setting.key), False))
370 safe_str(setting.section), safe_str(setting.key), False))
371 log.debug(
371 log.debug(
372 'settings ui from db: %s',
372 'settings ui from db: %s',
373 ','.join(map(lambda s: '[{}] {}={}'.format(*s), ui_data)))
373 ','.join(map(lambda s: '[{}] {}={}'.format(*s), ui_data)))
374 if clear_session:
374 if clear_session:
375 meta.Session.remove()
375 meta.Session.remove()
376
376
377 # TODO: mikhail: probably it makes no sense to re-read hooks information.
377 # TODO: mikhail: probably it makes no sense to re-read hooks information.
378 # It's already there and activated/deactivated
378 # It's already there and activated/deactivated
379 skip_entries = []
379 skip_entries = []
380 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
380 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
381 if 'pull' not in enabled_hook_classes:
381 if 'pull' not in enabled_hook_classes:
382 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
382 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
383 if 'push' not in enabled_hook_classes:
383 if 'push' not in enabled_hook_classes:
384 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
384 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
385 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
385 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
386 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
386 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
387
387
388 config = [entry for entry in config if entry[:2] not in skip_entries]
388 config = [entry for entry in config if entry[:2] not in skip_entries]
389
389
390 return config
390 return config
391
391
392
392
393 def make_db_config(clear_session=True, repo=None):
393 def make_db_config(clear_session=True, repo=None):
394 """
394 """
395 Create a :class:`Config` instance based on the values in the database.
395 Create a :class:`Config` instance based on the values in the database.
396 """
396 """
397 config = Config()
397 config = Config()
398 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
398 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
399 for section, option, value in config_data:
399 for section, option, value in config_data:
400 config.set(section, option, value)
400 config.set(section, option, value)
401 return config
401 return config
402
402
403
403
404 def get_enabled_hook_classes(ui_settings):
404 def get_enabled_hook_classes(ui_settings):
405 """
405 """
406 Return the enabled hook classes.
406 Return the enabled hook classes.
407
407
408 :param ui_settings: List of ui_settings as returned
408 :param ui_settings: List of ui_settings as returned
409 by :meth:`VcsSettingsModel.get_ui_settings`
409 by :meth:`VcsSettingsModel.get_ui_settings`
410
410
411 :return: a list with the enabled hook classes. The order is not guaranteed.
411 :return: a list with the enabled hook classes. The order is not guaranteed.
412 :rtype: list
412 :rtype: list
413 """
413 """
414 enabled_hooks = []
414 enabled_hooks = []
415 active_hook_keys = [
415 active_hook_keys = [
416 key for section, key, value, active in ui_settings
416 key for section, key, value, active in ui_settings
417 if section == 'hooks' and active]
417 if section == 'hooks' and active]
418
418
419 hook_names = {
419 hook_names = {
420 RhodeCodeUi.HOOK_PUSH: 'push',
420 RhodeCodeUi.HOOK_PUSH: 'push',
421 RhodeCodeUi.HOOK_PULL: 'pull',
421 RhodeCodeUi.HOOK_PULL: 'pull',
422 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
422 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
423 }
423 }
424
424
425 for key in active_hook_keys:
425 for key in active_hook_keys:
426 hook = hook_names.get(key)
426 hook = hook_names.get(key)
427 if hook:
427 if hook:
428 enabled_hooks.append(hook)
428 enabled_hooks.append(hook)
429
429
430 return enabled_hooks
430 return enabled_hooks
431
431
432
432
433 def set_rhodecode_config(config):
433 def set_rhodecode_config(config):
434 """
434 """
435 Updates pyramid config with new settings from database
435 Updates pyramid config with new settings from database
436
436
437 :param config:
437 :param config:
438 """
438 """
439 from rhodecode.model.settings import SettingsModel
439 from rhodecode.model.settings import SettingsModel
440 app_settings = SettingsModel().get_all_settings()
440 app_settings = SettingsModel().get_all_settings()
441
441
442 for k, v in app_settings.items():
442 for k, v in app_settings.items():
443 config[k] = v
443 config[k] = v
444
444
445
445
446 def get_rhodecode_realm():
446 def get_rhodecode_realm():
447 """
447 """
448 Return the rhodecode realm from database.
448 Return the rhodecode realm from database.
449 """
449 """
450 from rhodecode.model.settings import SettingsModel
450 from rhodecode.model.settings import SettingsModel
451 realm = SettingsModel().get_setting_by_name('realm')
451 realm = SettingsModel().get_setting_by_name('realm')
452 return safe_str(realm.app_settings_value)
452 return safe_str(realm.app_settings_value)
453
453
454
454
455 def get_rhodecode_base_path():
455 def get_rhodecode_base_path():
456 """
456 """
457 Returns the base path. The base path is the filesystem path which points
457 Returns the base path. The base path is the filesystem path which points
458 to the repository store.
458 to the repository store.
459 """
459 """
460 from rhodecode.model.settings import SettingsModel
460 from rhodecode.model.settings import SettingsModel
461 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
461 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
462 return safe_str(paths_ui.ui_value)
462 return safe_str(paths_ui.ui_value)
463
463
464
464
465 def map_groups(path):
465 def map_groups(path):
466 """
466 """
467 Given a full path to a repository, create all nested groups that this
467 Given a full path to a repository, create all nested groups that this
468 repo is inside. This function creates parent-child relationships between
468 repo is inside. This function creates parent-child relationships between
469 groups and creates default perms for all new groups.
469 groups and creates default perms for all new groups.
470
470
471 :param paths: full path to repository
471 :param paths: full path to repository
472 """
472 """
473 from rhodecode.model.repo_group import RepoGroupModel
473 from rhodecode.model.repo_group import RepoGroupModel
474 sa = meta.Session()
474 sa = meta.Session()
475 groups = path.split(Repository.NAME_SEP)
475 groups = path.split(Repository.NAME_SEP)
476 parent = None
476 parent = None
477 group = None
477 group = None
478
478
479 # last element is repo in nested groups structure
479 # last element is repo in nested groups structure
480 groups = groups[:-1]
480 groups = groups[:-1]
481 rgm = RepoGroupModel(sa)
481 rgm = RepoGroupModel(sa)
482 owner = User.get_first_super_admin()
482 owner = User.get_first_super_admin()
483 for lvl, group_name in enumerate(groups):
483 for lvl, group_name in enumerate(groups):
484 group_name = '/'.join(groups[:lvl] + [group_name])
484 group_name = '/'.join(groups[:lvl] + [group_name])
485 group = RepoGroup.get_by_group_name(group_name)
485 group = RepoGroup.get_by_group_name(group_name)
486 desc = '%s group' % group_name
486 desc = '%s group' % group_name
487
487
488 # skip folders that are now removed repos
488 # skip folders that are now removed repos
489 if REMOVED_REPO_PAT.match(group_name):
489 if REMOVED_REPO_PAT.match(group_name):
490 break
490 break
491
491
492 if group is None:
492 if group is None:
493 log.debug('creating group level: %s group_name: %s',
493 log.debug('creating group level: %s group_name: %s',
494 lvl, group_name)
494 lvl, group_name)
495 group = RepoGroup(group_name, parent)
495 group = RepoGroup(group_name, parent)
496 group.group_description = desc
496 group.group_description = desc
497 group.user = owner
497 group.user = owner
498 sa.add(group)
498 sa.add(group)
499 perm_obj = rgm._create_default_perms(group)
499 perm_obj = rgm._create_default_perms(group)
500 sa.add(perm_obj)
500 sa.add(perm_obj)
501 sa.flush()
501 sa.flush()
502
502
503 parent = group
503 parent = group
504 return group
504 return group
505
505
506
506
507 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
507 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
508 """
508 """
509 maps all repos given in initial_repo_list, non existing repositories
509 maps all repos given in initial_repo_list, non existing repositories
510 are created, if remove_obsolete is True it also checks for db entries
510 are created, if remove_obsolete is True it also checks for db entries
511 that are not in initial_repo_list and removes them.
511 that are not in initial_repo_list and removes them.
512
512
513 :param initial_repo_list: list of repositories found by scanning methods
513 :param initial_repo_list: list of repositories found by scanning methods
514 :param remove_obsolete: check for obsolete entries in database
514 :param remove_obsolete: check for obsolete entries in database
515 """
515 """
516 from rhodecode.model.repo import RepoModel
516 from rhodecode.model.repo import RepoModel
517 from rhodecode.model.repo_group import RepoGroupModel
517 from rhodecode.model.repo_group import RepoGroupModel
518 from rhodecode.model.settings import SettingsModel
518 from rhodecode.model.settings import SettingsModel
519
519
520 sa = meta.Session()
520 sa = meta.Session()
521 repo_model = RepoModel()
521 repo_model = RepoModel()
522 user = User.get_first_super_admin()
522 user = User.get_first_super_admin()
523 added = []
523 added = []
524
524
525 # creation defaults
525 # creation defaults
526 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
526 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
527 enable_statistics = defs.get('repo_enable_statistics')
527 enable_statistics = defs.get('repo_enable_statistics')
528 enable_locking = defs.get('repo_enable_locking')
528 enable_locking = defs.get('repo_enable_locking')
529 enable_downloads = defs.get('repo_enable_downloads')
529 enable_downloads = defs.get('repo_enable_downloads')
530 private = defs.get('repo_private')
530 private = defs.get('repo_private')
531
531
532 for name, repo in initial_repo_list.items():
532 for name, repo in initial_repo_list.items():
533 group = map_groups(name)
533 group = map_groups(name)
534 unicode_name = safe_unicode(name)
534 unicode_name = safe_unicode(name)
535 db_repo = repo_model.get_by_repo_name(unicode_name)
535 db_repo = repo_model.get_by_repo_name(unicode_name)
536 # found repo that is on filesystem not in RhodeCode database
536 # found repo that is on filesystem not in RhodeCode database
537 if not db_repo:
537 if not db_repo:
538 log.info('repository %s not found, creating now', name)
538 log.info('repository %s not found, creating now', name)
539 added.append(name)
539 added.append(name)
540 desc = (repo.description
540 desc = (repo.description
541 if repo.description != 'unknown'
541 if repo.description != 'unknown'
542 else '%s repository' % name)
542 else '%s repository' % name)
543
543
544 db_repo = repo_model._create_repo(
544 db_repo = repo_model._create_repo(
545 repo_name=name,
545 repo_name=name,
546 repo_type=repo.alias,
546 repo_type=repo.alias,
547 description=desc,
547 description=desc,
548 repo_group=getattr(group, 'group_id', None),
548 repo_group=getattr(group, 'group_id', None),
549 owner=user,
549 owner=user,
550 enable_locking=enable_locking,
550 enable_locking=enable_locking,
551 enable_downloads=enable_downloads,
551 enable_downloads=enable_downloads,
552 enable_statistics=enable_statistics,
552 enable_statistics=enable_statistics,
553 private=private,
553 private=private,
554 state=Repository.STATE_CREATED
554 state=Repository.STATE_CREATED
555 )
555 )
556 sa.commit()
556 sa.commit()
557 # we added that repo just now, and make sure we updated server info
557 # we added that repo just now, and make sure we updated server info
558 if db_repo.repo_type == 'git':
558 if db_repo.repo_type == 'git':
559 git_repo = db_repo.scm_instance()
559 git_repo = db_repo.scm_instance()
560 # update repository server-info
560 # update repository server-info
561 log.debug('Running update server info')
561 log.debug('Running update server info')
562 git_repo._update_server_info()
562 git_repo._update_server_info()
563
563
564 db_repo.update_commit_cache()
564 db_repo.update_commit_cache()
565
565
566 config = db_repo._config
566 config = db_repo._config
567 config.set('extensions', 'largefiles', '')
567 config.set('extensions', 'largefiles', '')
568 repo = db_repo.scm_instance(config=config)
568 repo = db_repo.scm_instance(config=config)
569 repo.install_hooks()
569 repo.install_hooks()
570
570
571 removed = []
571 removed = []
572 if remove_obsolete:
572 if remove_obsolete:
573 # remove from database those repositories that are not in the filesystem
573 # remove from database those repositories that are not in the filesystem
574 for repo in sa.query(Repository).all():
574 for repo in sa.query(Repository).all():
575 if repo.repo_name not in initial_repo_list.keys():
575 if repo.repo_name not in initial_repo_list.keys():
576 log.debug("Removing non-existing repository found in db `%s`",
576 log.debug("Removing non-existing repository found in db `%s`",
577 repo.repo_name)
577 repo.repo_name)
578 try:
578 try:
579 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
579 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
580 sa.commit()
580 sa.commit()
581 removed.append(repo.repo_name)
581 removed.append(repo.repo_name)
582 except Exception:
582 except Exception:
583 # don't hold further removals on error
583 # don't hold further removals on error
584 log.error(traceback.format_exc())
584 log.error(traceback.format_exc())
585 sa.rollback()
585 sa.rollback()
586
586
587 def splitter(full_repo_name):
587 def splitter(full_repo_name):
588 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
588 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
589 gr_name = None
589 gr_name = None
590 if len(_parts) == 2:
590 if len(_parts) == 2:
591 gr_name = _parts[0]
591 gr_name = _parts[0]
592 return gr_name
592 return gr_name
593
593
594 initial_repo_group_list = [splitter(x) for x in
594 initial_repo_group_list = [splitter(x) for x in
595 initial_repo_list.keys() if splitter(x)]
595 initial_repo_list.keys() if splitter(x)]
596
596
597 # remove from database those repository groups that are not in the
597 # remove from database those repository groups that are not in the
598 # filesystem due to parent child relationships we need to delete them
598 # filesystem due to parent child relationships we need to delete them
599 # in a specific order of most nested first
599 # in a specific order of most nested first
600 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
600 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
601 nested_sort = lambda gr: len(gr.split('/'))
601 nested_sort = lambda gr: len(gr.split('/'))
602 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
602 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
603 if group_name not in initial_repo_group_list:
603 if group_name not in initial_repo_group_list:
604 repo_group = RepoGroup.get_by_group_name(group_name)
604 repo_group = RepoGroup.get_by_group_name(group_name)
605 if (repo_group.children.all() or
605 if (repo_group.children.all() or
606 not RepoGroupModel().check_exist_filesystem(
606 not RepoGroupModel().check_exist_filesystem(
607 group_name=group_name, exc_on_failure=False)):
607 group_name=group_name, exc_on_failure=False)):
608 continue
608 continue
609
609
610 log.info(
610 log.info(
611 'Removing non-existing repository group found in db `%s`',
611 'Removing non-existing repository group found in db `%s`',
612 group_name)
612 group_name)
613 try:
613 try:
614 RepoGroupModel(sa).delete(group_name, fs_remove=False)
614 RepoGroupModel(sa).delete(group_name, fs_remove=False)
615 sa.commit()
615 sa.commit()
616 removed.append(group_name)
616 removed.append(group_name)
617 except Exception:
617 except Exception:
618 # don't hold further removals on error
618 # don't hold further removals on error
619 log.exception(
619 log.exception(
620 'Unable to remove repository group `%s`',
620 'Unable to remove repository group `%s`',
621 group_name)
621 group_name)
622 sa.rollback()
622 sa.rollback()
623 raise
623 raise
624
624
625 return added, removed
625 return added, removed
626
626
627
627
628 def load_rcextensions(root_path):
628 def load_rcextensions(root_path):
629 import rhodecode
629 import rhodecode
630 from rhodecode.config import conf
630 from rhodecode.config import conf
631
631
632 path = os.path.join(root_path, 'rcextensions', '__init__.py')
632 path = os.path.join(root_path, 'rcextensions', '__init__.py')
633 if os.path.isfile(path):
633 if os.path.isfile(path):
634 rcext = create_module('rc', path)
634 rcext = create_module('rc', path)
635 EXT = rhodecode.EXTENSIONS = rcext
635 EXT = rhodecode.EXTENSIONS = rcext
636 log.debug('Found rcextensions now loading %s...', rcext)
636 log.debug('Found rcextensions now loading %s...', rcext)
637
637
638 # Additional mappings that are not present in the pygments lexers
638 # Additional mappings that are not present in the pygments lexers
639 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
639 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
640
640
641 # auto check if the module is not missing any data, set to default if is
641 # auto check if the module is not missing any data, set to default if is
642 # this will help autoupdate new feature of rcext module
642 # this will help autoupdate new feature of rcext module
643 #from rhodecode.config import rcextensions
643 #from rhodecode.config import rcextensions
644 #for k in dir(rcextensions):
644 #for k in dir(rcextensions):
645 # if not k.startswith('_') and not hasattr(EXT, k):
645 # if not k.startswith('_') and not hasattr(EXT, k):
646 # setattr(EXT, k, getattr(rcextensions, k))
646 # setattr(EXT, k, getattr(rcextensions, k))
647
647
648
648
649 def get_custom_lexer(extension):
649 def get_custom_lexer(extension):
650 """
650 """
651 returns a custom lexer if it is defined in rcextensions module, or None
651 returns a custom lexer if it is defined in rcextensions module, or None
652 if there's no custom lexer defined
652 if there's no custom lexer defined
653 """
653 """
654 import rhodecode
654 import rhodecode
655 from pygments import lexers
655 from pygments import lexers
656
656
657 # custom override made by RhodeCode
657 # custom override made by RhodeCode
658 if extension in ['mako']:
658 if extension in ['mako']:
659 return lexers.get_lexer_by_name('html+mako')
659 return lexers.get_lexer_by_name('html+mako')
660
660
661 # check if we didn't define this extension as other lexer
661 # check if we didn't define this extension as other lexer
662 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
662 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
663 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
663 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
664 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
664 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
665 return lexers.get_lexer_by_name(_lexer_name)
665 return lexers.get_lexer_by_name(_lexer_name)
666
666
667
667
668 #==============================================================================
668 #==============================================================================
669 # TEST FUNCTIONS AND CREATORS
669 # TEST FUNCTIONS AND CREATORS
670 #==============================================================================
670 #==============================================================================
671 def create_test_index(repo_location, config):
671 def create_test_index(repo_location, config):
672 """
672 """
673 Makes default test index.
673 Makes default test index.
674 """
674 """
675 import rc_testdata
675 import rc_testdata
676
676
677 rc_testdata.extract_search_index(
677 rc_testdata.extract_search_index(
678 'vcs_search_index', os.path.dirname(config['search.location']))
678 'vcs_search_index', os.path.dirname(config['search.location']))
679
679
680
680
681 def create_test_directory(test_path):
681 def create_test_directory(test_path):
682 """
682 """
683 Create test directory if it doesn't exist.
683 Create test directory if it doesn't exist.
684 """
684 """
685 if not os.path.isdir(test_path):
685 if not os.path.isdir(test_path):
686 log.debug('Creating testdir %s', test_path)
686 log.debug('Creating testdir %s', test_path)
687 os.makedirs(test_path)
687 os.makedirs(test_path)
688
688
689
689
690 def create_test_database(test_path, config):
690 def create_test_database(test_path, config):
691 """
691 """
692 Makes a fresh database.
692 Makes a fresh database.
693 """
693 """
694 from rhodecode.lib.db_manage import DbManage
694 from rhodecode.lib.db_manage import DbManage
695
695
696 # PART ONE create db
696 # PART ONE create db
697 dbconf = config['sqlalchemy.db1.url']
697 dbconf = config['sqlalchemy.db1.url']
698 log.debug('making test db %s', dbconf)
698 log.debug('making test db %s', dbconf)
699
699
700 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
700 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
701 tests=True, cli_args={'force_ask': True})
701 tests=True, cli_args={'force_ask': True})
702 dbmanage.create_tables(override=True)
702 dbmanage.create_tables(override=True)
703 dbmanage.set_db_version()
703 dbmanage.set_db_version()
704 # for tests dynamically set new root paths based on generated content
704 # for tests dynamically set new root paths based on generated content
705 dbmanage.create_settings(dbmanage.config_prompt(test_path))
705 dbmanage.create_settings(dbmanage.config_prompt(test_path))
706 dbmanage.create_default_user()
706 dbmanage.create_default_user()
707 dbmanage.create_test_admin_and_users()
707 dbmanage.create_test_admin_and_users()
708 dbmanage.create_permissions()
708 dbmanage.create_permissions()
709 dbmanage.populate_default_permissions()
709 dbmanage.populate_default_permissions()
710 Session().commit()
710 Session().commit()
711
711
712
712
713 def create_test_repositories(test_path, config):
713 def create_test_repositories(test_path, config):
714 """
714 """
715 Creates test repositories in the temporary directory. Repositories are
715 Creates test repositories in the temporary directory. Repositories are
716 extracted from archives within the rc_testdata package.
716 extracted from archives within the rc_testdata package.
717 """
717 """
718 import rc_testdata
718 import rc_testdata
719 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
719 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
720
720
721 log.debug('making test vcs repositories')
721 log.debug('making test vcs repositories')
722
722
723 idx_path = config['search.location']
723 idx_path = config['search.location']
724 data_path = config['cache_dir']
724 data_path = config['cache_dir']
725
725
726 # clean index and data
726 # clean index and data
727 if idx_path and os.path.exists(idx_path):
727 if idx_path and os.path.exists(idx_path):
728 log.debug('remove %s', idx_path)
728 log.debug('remove %s', idx_path)
729 shutil.rmtree(idx_path)
729 shutil.rmtree(idx_path)
730
730
731 if data_path and os.path.exists(data_path):
731 if data_path and os.path.exists(data_path):
732 log.debug('remove %s', data_path)
732 log.debug('remove %s', data_path)
733 shutil.rmtree(data_path)
733 shutil.rmtree(data_path)
734
734
735 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
735 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
736 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
736 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
737
737
738 # Note: Subversion is in the process of being integrated with the system,
738 # Note: Subversion is in the process of being integrated with the system,
739 # until we have a properly packed version of the test svn repository, this
739 # until we have a properly packed version of the test svn repository, this
740 # tries to copy over the repo from a package "rc_testdata"
740 # tries to copy over the repo from a package "rc_testdata"
741 svn_repo_path = rc_testdata.get_svn_repo_archive()
741 svn_repo_path = rc_testdata.get_svn_repo_archive()
742 with tarfile.open(svn_repo_path) as tar:
742 with tarfile.open(svn_repo_path) as tar:
743 tar.extractall(jn(test_path, SVN_REPO))
743 tar.extractall(jn(test_path, SVN_REPO))
744
744
745
745
746 def password_changed(auth_user, session):
746 def password_changed(auth_user, session):
747 # Never report password change in case of default user or anonymous user.
747 # Never report password change in case of default user or anonymous user.
748 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
748 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
749 return False
749 return False
750
750
751 password_hash = md5(auth_user.password) if auth_user.password else None
751 password_hash = md5(auth_user.password) if auth_user.password else None
752 rhodecode_user = session.get('rhodecode_user', {})
752 rhodecode_user = session.get('rhodecode_user', {})
753 session_password_hash = rhodecode_user.get('password', '')
753 session_password_hash = rhodecode_user.get('password', '')
754 return password_hash != session_password_hash
754 return password_hash != session_password_hash
755
755
756
756
757 def read_opensource_licenses():
757 def read_opensource_licenses():
758 global _license_cache
758 global _license_cache
759
759
760 if not _license_cache:
760 if not _license_cache:
761 licenses = pkg_resources.resource_string(
761 licenses = pkg_resources.resource_string(
762 'rhodecode', 'config/licenses.json')
762 'rhodecode', 'config/licenses.json')
763 _license_cache = json.loads(licenses)
763 _license_cache = json.loads(licenses)
764
764
765 return _license_cache
765 return _license_cache
766
766
767
767
768 def generate_platform_uuid():
768 def generate_platform_uuid():
769 """
769 """
770 Generates platform UUID based on it's name
770 Generates platform UUID based on it's name
771 """
771 """
772 import platform
772 import platform
773
773
774 try:
774 try:
775 uuid_list = [platform.platform()]
775 uuid_list = [platform.platform()]
776 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
776 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
777 except Exception as e:
777 except Exception as e:
778 log.error('Failed to generate host uuid: %s' % e)
778 log.error('Failed to generate host uuid: %s' % e)
779 return 'UNDEFINED'
779 return 'UNDEFINED'
@@ -1,322 +1,324 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 import io
20 import io
21 import re
21 import re
22 import datetime
22 import datetime
23 import logging
23 import logging
24 import Queue
24 import Queue
25 import subprocess32
25 import subprocess32
26 import os
26 import os
27
27
28
28
29 from dateutil.parser import parse
29 from dateutil.parser import parse
30 from pyramid.i18n import get_localizer
30 from pyramid.i18n import get_localizer
31 from pyramid.threadlocal import get_current_request
31 from pyramid.threadlocal import get_current_request
32 from pyramid.interfaces import IRoutesMapper
32 from pyramid.interfaces import IRoutesMapper
33 from pyramid.settings import asbool
33 from pyramid.settings import asbool
34 from pyramid.path import AssetResolver
34 from pyramid.path import AssetResolver
35 from threading import Thread
35 from threading import Thread
36
36
37 from rhodecode.translation import _ as tsf
37 from rhodecode.translation import _ as tsf
38 from rhodecode.config.jsroutes import generate_jsroutes_content
38 from rhodecode.config.jsroutes import generate_jsroutes_content
39 from rhodecode.lib import auth
39 from rhodecode.lib import auth
40 from rhodecode.lib.base import get_auth_user
40 from rhodecode.lib.base import get_auth_user
41
41
42
42
43 import rhodecode
43 import rhodecode
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 def add_renderer_globals(event):
49 def add_renderer_globals(event):
50 from rhodecode.lib import helpers
50 from rhodecode.lib import helpers
51
51
52 # TODO: When executed in pyramid view context the request is not available
52 # TODO: When executed in pyramid view context the request is not available
53 # in the event. Find a better solution to get the request.
53 # in the event. Find a better solution to get the request.
54 request = event['request'] or get_current_request()
54 request = event['request'] or get_current_request()
55
55
56 # Add Pyramid translation as '_' to context
56 # Add Pyramid translation as '_' to context
57 event['_'] = request.translate
57 event['_'] = request.translate
58 event['_ungettext'] = request.plularize
58 event['_ungettext'] = request.plularize
59 event['h'] = helpers
59 event['h'] = helpers
60
60
61
61
62 def add_localizer(event):
62 def add_localizer(event):
63 request = event.request
63 request = event.request
64 localizer = request.localizer
64 localizer = request.localizer
65
65
66 def auto_translate(*args, **kwargs):
66 def auto_translate(*args, **kwargs):
67 return localizer.translate(tsf(*args, **kwargs))
67 return localizer.translate(tsf(*args, **kwargs))
68
68
69 request.translate = auto_translate
69 request.translate = auto_translate
70 request.plularize = localizer.pluralize
70 request.plularize = localizer.pluralize
71
71
72
72
73 def set_user_lang(event):
73 def set_user_lang(event):
74 request = event.request
74 request = event.request
75 cur_user = getattr(request, 'user', None)
75 cur_user = getattr(request, 'user', None)
76
76
77 if cur_user:
77 if cur_user:
78 user_lang = cur_user.get_instance().user_data.get('language')
78 user_lang = cur_user.get_instance().user_data.get('language')
79 if user_lang:
79 if user_lang:
80 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
80 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
81 event.request._LOCALE_ = user_lang
81 event.request._LOCALE_ = user_lang
82
82
83
83
84 def add_request_user_context(event):
84 def add_request_user_context(event):
85 """
85 """
86 Adds auth user into request context
86 Adds auth user into request context
87 """
87 """
88 request = event.request
88 request = event.request
89 # access req_id as soon as possible
90 req_id = request.req_id
89
91
90 if hasattr(request, 'vcs_call'):
92 if hasattr(request, 'vcs_call'):
91 # skip vcs calls
93 # skip vcs calls
92 return
94 return
93
95
94 if hasattr(request, 'rpc_method'):
96 if hasattr(request, 'rpc_method'):
95 # skip api calls
97 # skip api calls
96 return
98 return
97
99
98 auth_user = get_auth_user(request)
100 auth_user = get_auth_user(request)
99 request.user = auth_user
101 request.user = auth_user
100 request.environ['rc_auth_user'] = auth_user
102 request.environ['rc_auth_user'] = auth_user
101
103 request.environ['rc_req_id'] = req_id
102
104
103 def inject_app_settings(event):
105 def inject_app_settings(event):
104 settings = event.app.registry.settings
106 settings = event.app.registry.settings
105 # inject info about available permissions
107 # inject info about available permissions
106 auth.set_available_permissions(settings)
108 auth.set_available_permissions(settings)
107
109
108
110
109 def scan_repositories_if_enabled(event):
111 def scan_repositories_if_enabled(event):
110 """
112 """
111 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
113 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
112 does a repository scan if enabled in the settings.
114 does a repository scan if enabled in the settings.
113 """
115 """
114 settings = event.app.registry.settings
116 settings = event.app.registry.settings
115 vcs_server_enabled = settings['vcs.server.enable']
117 vcs_server_enabled = settings['vcs.server.enable']
116 import_on_startup = settings['startup.import_repos']
118 import_on_startup = settings['startup.import_repos']
117 if vcs_server_enabled and import_on_startup:
119 if vcs_server_enabled and import_on_startup:
118 from rhodecode.model.scm import ScmModel
120 from rhodecode.model.scm import ScmModel
119 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
121 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
120 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
122 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
121 repo2db_mapper(repositories, remove_obsolete=False)
123 repo2db_mapper(repositories, remove_obsolete=False)
122
124
123
125
124 def write_metadata_if_needed(event):
126 def write_metadata_if_needed(event):
125 """
127 """
126 Writes upgrade metadata
128 Writes upgrade metadata
127 """
129 """
128 import rhodecode
130 import rhodecode
129 from rhodecode.lib import system_info
131 from rhodecode.lib import system_info
130 from rhodecode.lib import ext_json
132 from rhodecode.lib import ext_json
131
133
132 fname = '.rcmetadata.json'
134 fname = '.rcmetadata.json'
133 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
135 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
134 metadata_destination = os.path.join(ini_loc, fname)
136 metadata_destination = os.path.join(ini_loc, fname)
135
137
136 def get_update_age():
138 def get_update_age():
137 now = datetime.datetime.utcnow()
139 now = datetime.datetime.utcnow()
138
140
139 with open(metadata_destination, 'rb') as f:
141 with open(metadata_destination, 'rb') as f:
140 data = ext_json.json.loads(f.read())
142 data = ext_json.json.loads(f.read())
141 if 'created_on' in data:
143 if 'created_on' in data:
142 update_date = parse(data['created_on'])
144 update_date = parse(data['created_on'])
143 diff = now - update_date
145 diff = now - update_date
144 return diff.total_seconds() / 60.0
146 return diff.total_seconds() / 60.0
145
147
146 return 0
148 return 0
147
149
148 def write():
150 def write():
149 configuration = system_info.SysInfo(
151 configuration = system_info.SysInfo(
150 system_info.rhodecode_config)()['value']
152 system_info.rhodecode_config)()['value']
151 license_token = configuration['config']['license_token']
153 license_token = configuration['config']['license_token']
152
154
153 setup = dict(
155 setup = dict(
154 workers=configuration['config']['server:main'].get(
156 workers=configuration['config']['server:main'].get(
155 'workers', '?'),
157 'workers', '?'),
156 worker_type=configuration['config']['server:main'].get(
158 worker_type=configuration['config']['server:main'].get(
157 'worker_class', 'sync'),
159 'worker_class', 'sync'),
158 )
160 )
159 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
161 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
160 del dbinfo['url']
162 del dbinfo['url']
161
163
162 metadata = dict(
164 metadata = dict(
163 desc='upgrade metadata info',
165 desc='upgrade metadata info',
164 license_token=license_token,
166 license_token=license_token,
165 created_on=datetime.datetime.utcnow().isoformat(),
167 created_on=datetime.datetime.utcnow().isoformat(),
166 usage=system_info.SysInfo(system_info.usage_info)()['value'],
168 usage=system_info.SysInfo(system_info.usage_info)()['value'],
167 platform=system_info.SysInfo(system_info.platform_type)()['value'],
169 platform=system_info.SysInfo(system_info.platform_type)()['value'],
168 database=dbinfo,
170 database=dbinfo,
169 cpu=system_info.SysInfo(system_info.cpu)()['value'],
171 cpu=system_info.SysInfo(system_info.cpu)()['value'],
170 memory=system_info.SysInfo(system_info.memory)()['value'],
172 memory=system_info.SysInfo(system_info.memory)()['value'],
171 setup=setup
173 setup=setup
172 )
174 )
173
175
174 with open(metadata_destination, 'wb') as f:
176 with open(metadata_destination, 'wb') as f:
175 f.write(ext_json.json.dumps(metadata))
177 f.write(ext_json.json.dumps(metadata))
176
178
177 settings = event.app.registry.settings
179 settings = event.app.registry.settings
178 if settings.get('metadata.skip'):
180 if settings.get('metadata.skip'):
179 return
181 return
180
182
181 # only write this every 24h, workers restart caused unwanted delays
183 # only write this every 24h, workers restart caused unwanted delays
182 try:
184 try:
183 age_in_min = get_update_age()
185 age_in_min = get_update_age()
184 except Exception:
186 except Exception:
185 age_in_min = 0
187 age_in_min = 0
186
188
187 if age_in_min > 60 * 60 * 24:
189 if age_in_min > 60 * 60 * 24:
188 return
190 return
189
191
190 try:
192 try:
191 write()
193 write()
192 except Exception:
194 except Exception:
193 pass
195 pass
194
196
195
197
196 def write_js_routes_if_enabled(event):
198 def write_js_routes_if_enabled(event):
197 registry = event.app.registry
199 registry = event.app.registry
198
200
199 mapper = registry.queryUtility(IRoutesMapper)
201 mapper = registry.queryUtility(IRoutesMapper)
200 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
202 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
201
203
202 def _extract_route_information(route):
204 def _extract_route_information(route):
203 """
205 """
204 Convert a route into tuple(name, path, args), eg:
206 Convert a route into tuple(name, path, args), eg:
205 ('show_user', '/profile/%(username)s', ['username'])
207 ('show_user', '/profile/%(username)s', ['username'])
206 """
208 """
207
209
208 routepath = route.pattern
210 routepath = route.pattern
209 pattern = route.pattern
211 pattern = route.pattern
210
212
211 def replace(matchobj):
213 def replace(matchobj):
212 if matchobj.group(1):
214 if matchobj.group(1):
213 return "%%(%s)s" % matchobj.group(1).split(':')[0]
215 return "%%(%s)s" % matchobj.group(1).split(':')[0]
214 else:
216 else:
215 return "%%(%s)s" % matchobj.group(2)
217 return "%%(%s)s" % matchobj.group(2)
216
218
217 routepath = _argument_prog.sub(replace, routepath)
219 routepath = _argument_prog.sub(replace, routepath)
218
220
219 if not routepath.startswith('/'):
221 if not routepath.startswith('/'):
220 routepath = '/'+routepath
222 routepath = '/'+routepath
221
223
222 return (
224 return (
223 route.name,
225 route.name,
224 routepath,
226 routepath,
225 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
227 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
226 for arg in _argument_prog.findall(pattern)]
228 for arg in _argument_prog.findall(pattern)]
227 )
229 )
228
230
229 def get_routes():
231 def get_routes():
230 # pyramid routes
232 # pyramid routes
231 for route in mapper.get_routes():
233 for route in mapper.get_routes():
232 if not route.name.startswith('__'):
234 if not route.name.startswith('__'):
233 yield _extract_route_information(route)
235 yield _extract_route_information(route)
234
236
235 if asbool(registry.settings.get('generate_js_files', 'false')):
237 if asbool(registry.settings.get('generate_js_files', 'false')):
236 static_path = AssetResolver().resolve('rhodecode:public').abspath()
238 static_path = AssetResolver().resolve('rhodecode:public').abspath()
237 jsroutes = get_routes()
239 jsroutes = get_routes()
238 jsroutes_file_content = generate_jsroutes_content(jsroutes)
240 jsroutes_file_content = generate_jsroutes_content(jsroutes)
239 jsroutes_file_path = os.path.join(
241 jsroutes_file_path = os.path.join(
240 static_path, 'js', 'rhodecode', 'routes.js')
242 static_path, 'js', 'rhodecode', 'routes.js')
241
243
242 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
244 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
243 f.write(jsroutes_file_content)
245 f.write(jsroutes_file_content)
244
246
245
247
246 class Subscriber(object):
248 class Subscriber(object):
247 """
249 """
248 Base class for subscribers to the pyramid event system.
250 Base class for subscribers to the pyramid event system.
249 """
251 """
250 def __call__(self, event):
252 def __call__(self, event):
251 self.run(event)
253 self.run(event)
252
254
253 def run(self, event):
255 def run(self, event):
254 raise NotImplementedError('Subclass has to implement this.')
256 raise NotImplementedError('Subclass has to implement this.')
255
257
256
258
257 class AsyncSubscriber(Subscriber):
259 class AsyncSubscriber(Subscriber):
258 """
260 """
259 Subscriber that handles the execution of events in a separate task to not
261 Subscriber that handles the execution of events in a separate task to not
260 block the execution of the code which triggers the event. It puts the
262 block the execution of the code which triggers the event. It puts the
261 received events into a queue from which the worker process takes them in
263 received events into a queue from which the worker process takes them in
262 order.
264 order.
263 """
265 """
264 def __init__(self):
266 def __init__(self):
265 self._stop = False
267 self._stop = False
266 self._eventq = Queue.Queue()
268 self._eventq = Queue.Queue()
267 self._worker = self.create_worker()
269 self._worker = self.create_worker()
268 self._worker.start()
270 self._worker.start()
269
271
270 def __call__(self, event):
272 def __call__(self, event):
271 self._eventq.put(event)
273 self._eventq.put(event)
272
274
273 def create_worker(self):
275 def create_worker(self):
274 worker = Thread(target=self.do_work)
276 worker = Thread(target=self.do_work)
275 worker.daemon = True
277 worker.daemon = True
276 return worker
278 return worker
277
279
278 def stop_worker(self):
280 def stop_worker(self):
279 self._stop = False
281 self._stop = False
280 self._eventq.put(None)
282 self._eventq.put(None)
281 self._worker.join()
283 self._worker.join()
282
284
283 def do_work(self):
285 def do_work(self):
284 while not self._stop:
286 while not self._stop:
285 event = self._eventq.get()
287 event = self._eventq.get()
286 if event is not None:
288 if event is not None:
287 self.run(event)
289 self.run(event)
288
290
289
291
290 class AsyncSubprocessSubscriber(AsyncSubscriber):
292 class AsyncSubprocessSubscriber(AsyncSubscriber):
291 """
293 """
292 Subscriber that uses the subprocess32 module to execute a command if an
294 Subscriber that uses the subprocess32 module to execute a command if an
293 event is received. Events are handled asynchronously.
295 event is received. Events are handled asynchronously.
294 """
296 """
295
297
296 def __init__(self, cmd, timeout=None):
298 def __init__(self, cmd, timeout=None):
297 super(AsyncSubprocessSubscriber, self).__init__()
299 super(AsyncSubprocessSubscriber, self).__init__()
298 self._cmd = cmd
300 self._cmd = cmd
299 self._timeout = timeout
301 self._timeout = timeout
300
302
301 def run(self, event):
303 def run(self, event):
302 cmd = self._cmd
304 cmd = self._cmd
303 timeout = self._timeout
305 timeout = self._timeout
304 log.debug('Executing command %s.', cmd)
306 log.debug('Executing command %s.', cmd)
305
307
306 try:
308 try:
307 output = subprocess32.check_output(
309 output = subprocess32.check_output(
308 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
310 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
309 log.debug('Command finished %s', cmd)
311 log.debug('Command finished %s', cmd)
310 if output:
312 if output:
311 log.debug('Command output: %s', output)
313 log.debug('Command output: %s', output)
312 except subprocess32.TimeoutExpired as e:
314 except subprocess32.TimeoutExpired as e:
313 log.exception('Timeout while executing command.')
315 log.exception('Timeout while executing command.')
314 if e.output:
316 if e.output:
315 log.error('Command output: %s', e.output)
317 log.error('Command output: %s', e.output)
316 except subprocess32.CalledProcessError as e:
318 except subprocess32.CalledProcessError as e:
317 log.exception('Error while executing command.')
319 log.exception('Error while executing command.')
318 if e.output:
320 if e.output:
319 log.error('Command output: %s', e.output)
321 log.error('Command output: %s', e.output)
320 except:
322 except:
321 log.exception(
323 log.exception(
322 'Exception while executing command %s.', cmd)
324 'Exception while executing command %s.', cmd)
General Comments 0
You need to be logged in to leave comments. Login now