##// END OF EJS Templates
config: Use rhodecode aslist if commas included in list settings otherwise pyramids aslist.
Martin Bornhold -
r604:d8ead9a9 default
parent child Browse files
Show More
@@ -1,462 +1,465 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Pylons middleware initialization
23 23 """
24 24 import logging
25 25 from collections import OrderedDict
26 26
27 27 from paste.registry import RegistryManager
28 28 from paste.gzipper import make_gzip_middleware
29 29 from pylons.wsgiapp import PylonsApp
30 30 from pyramid.authorization import ACLAuthorizationPolicy
31 31 from pyramid.config import Configurator
32 32 from pyramid.settings import asbool, aslist
33 33 from pyramid.wsgi import wsgiapp
34 34 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError
35 35 from pylons.controllers.util import redirect
36 36 from pyramid.events import ApplicationCreated
37 37 import pyramid.httpexceptions as httpexceptions
38 38 from pyramid.renderers import render_to_response
39 39 from routes.middleware import RoutesMiddleware
40 40 import routes.util
41 41
42 42 import rhodecode
43 43 import rhodecode.integrations # do not remove this as it registers celery tasks
44 44 from rhodecode.config import patches
45 45 from rhodecode.config.routing import STATIC_FILE_PREFIX
46 46 from rhodecode.config.environment import (
47 47 load_environment, load_pyramid_environment)
48 48 from rhodecode.lib.middleware import csrf
49 49 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
50 50 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
51 51 from rhodecode.lib.middleware.https_fixup import HttpsFixup
52 52 from rhodecode.lib.middleware.vcs import VCSMiddleware
53 53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
54 55 from rhodecode.subscribers import scan_repositories_if_enabled
55 56
56 57
57 58 log = logging.getLogger(__name__)
58 59
59 60
60 61 # this is used to avoid avoid the route lookup overhead in routesmiddleware
61 62 # for certain routes which won't go to pylons to - eg. static files, debugger
62 63 # it is only needed for the pylons migration and can be removed once complete
63 64 class SkippableRoutesMiddleware(RoutesMiddleware):
64 65 """ Routes middleware that allows you to skip prefixes """
65 66
66 67 def __init__(self, *args, **kw):
67 68 self.skip_prefixes = kw.pop('skip_prefixes', [])
68 69 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
69 70
70 71 def __call__(self, environ, start_response):
71 72 for prefix in self.skip_prefixes:
72 73 if environ['PATH_INFO'].startswith(prefix):
73 74 # added to avoid the case when a missing /_static route falls
74 75 # through to pylons and causes an exception as pylons is
75 76 # expecting wsgiorg.routingargs to be set in the environ
76 77 # by RoutesMiddleware.
77 78 if 'wsgiorg.routing_args' not in environ:
78 79 environ['wsgiorg.routing_args'] = (None, {})
79 80 return self.app(environ, start_response)
80 81
81 82 return super(SkippableRoutesMiddleware, self).__call__(
82 83 environ, start_response)
83 84
84 85
85 86 def make_app(global_conf, static_files=True, **app_conf):
86 87 """Create a Pylons WSGI application and return it
87 88
88 89 ``global_conf``
89 90 The inherited configuration for this application. Normally from
90 91 the [DEFAULT] section of the Paste ini file.
91 92
92 93 ``app_conf``
93 94 The application's local configuration. Normally specified in
94 95 the [app:<name>] section of the Paste ini file (where <name>
95 96 defaults to main).
96 97
97 98 """
98 99 # Apply compatibility patches
99 100 patches.kombu_1_5_1_python_2_7_11()
100 101 patches.inspect_getargspec()
101 102
102 103 # Configure the Pylons environment
103 104 config = load_environment(global_conf, app_conf)
104 105
105 106 # The Pylons WSGI app
106 107 app = PylonsApp(config=config)
107 108 if rhodecode.is_test:
108 109 app = csrf.CSRFDetector(app)
109 110
110 111 expected_origin = config.get('expected_origin')
111 112 if expected_origin:
112 113 # The API can be accessed from other Origins.
113 114 app = csrf.OriginChecker(app, expected_origin,
114 115 skip_urls=[routes.util.url_for('api')])
115 116
116 117 # Establish the Registry for this application
117 118 app = RegistryManager(app)
118 119
119 120 app.config = config
120 121
121 122 return app
122 123
123 124
124 125 def make_pyramid_app(global_config, **settings):
125 126 """
126 127 Constructs the WSGI application based on Pyramid and wraps the Pylons based
127 128 application.
128 129
129 130 Specials:
130 131
131 132 * We migrate from Pylons to Pyramid. While doing this, we keep both
132 133 frameworks functional. This involves moving some WSGI middlewares around
133 134 and providing access to some data internals, so that the old code is
134 135 still functional.
135 136
136 137 * The application can also be integrated like a plugin via the call to
137 138 `includeme`. This is accompanied with the other utility functions which
138 139 are called. Changing this should be done with great care to not break
139 140 cases when these fragments are assembled from another place.
140 141
141 142 """
142 143 # The edition string should be available in pylons too, so we add it here
143 144 # before copying the settings.
144 145 settings.setdefault('rhodecode.edition', 'Community Edition')
145 146
146 147 # As long as our Pylons application does expect "unprepared" settings, make
147 148 # sure that we keep an unmodified copy. This avoids unintentional change of
148 149 # behavior in the old application.
149 150 settings_pylons = settings.copy()
150 151
151 152 sanitize_settings_and_apply_defaults(settings)
152 153 config = Configurator(settings=settings)
153 154 add_pylons_compat_data(config.registry, global_config, settings_pylons)
154 155
155 156 load_pyramid_environment(global_config, settings)
156 157
157 158 includeme_first(config)
158 159 includeme(config)
159 160 pyramid_app = config.make_wsgi_app()
160 161 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
161 162 return pyramid_app
162 163
163 164
164 165 def make_not_found_view(config):
165 166 """
166 167 This creates the view which should be registered as not-found-view to
167 168 pyramid. Basically it contains of the old pylons app, converted to a view.
168 169 Additionally it is wrapped by some other middlewares.
169 170 """
170 171 settings = config.registry.settings
171 172 vcs_server_enabled = settings['vcs.server.enable']
172 173
173 174 # Make pylons app from unprepared settings.
174 175 pylons_app = make_app(
175 176 config.registry._pylons_compat_global_config,
176 177 **config.registry._pylons_compat_settings)
177 178 config.registry._pylons_compat_config = pylons_app.config
178 179
179 180 # Appenlight monitoring.
180 181 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
181 182 pylons_app, settings)
182 183
183 184 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find
184 185 # a view to handle the request. Therefore we wrap it around the pylons app.
185 186 if vcs_server_enabled:
186 187 pylons_app = VCSMiddleware(
187 188 pylons_app, settings, appenlight_client, registry=config.registry)
188 189
189 190 pylons_app_as_view = wsgiapp(pylons_app)
190 191
191 192 # Protect from VCS Server error related pages when server is not available
192 193 if not vcs_server_enabled:
193 194 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
194 195
195 196 def pylons_app_with_error_handler(context, request):
196 197 """
197 198 Handle exceptions from rc pylons app:
198 199
199 200 - old webob type exceptions get converted to pyramid exceptions
200 201 - pyramid exceptions are passed to the error handler view
201 202 """
202 203 def is_vcs_response(request):
203 204 return True == request.environ.get(
204 205 'rhodecode.vcs.skip_error_handling')
205 206
206 207 def is_webob_error(response):
207 208 # webob type error responses
208 209 return (400 <= response.status_int <= 599)
209 210
210 211 try:
211 212 response = pylons_app_as_view(context, request)
212 213 if is_webob_error(response) and not is_vcs_response(request):
213 214 return error_handler(
214 215 webob_to_pyramid_http_response(response), request)
215 216 except HTTPError as e: # pyramid type exceptions
216 217 return error_handler(e, request)
217 218 except Exception:
218 219 if settings.get('debugtoolbar.enabled', False):
219 220 raise
220 221 return error_handler(HTTPInternalServerError(), request)
221 222 return response
222 223
223 224 return pylons_app_with_error_handler
224 225
225 226
226 227 def add_pylons_compat_data(registry, global_config, settings):
227 228 """
228 229 Attach data to the registry to support the Pylons integration.
229 230 """
230 231 registry._pylons_compat_global_config = global_config
231 232 registry._pylons_compat_settings = settings
232 233
233 234
234 235 def webob_to_pyramid_http_response(webob_response):
235 236 ResponseClass = httpexceptions.status_map[webob_response.status_int]
236 237 pyramid_response = ResponseClass(webob_response.status)
237 238 pyramid_response.status = webob_response.status
238 239 pyramid_response.headers.update(webob_response.headers)
239 240 if pyramid_response.headers['content-type'] == 'text/html':
240 241 pyramid_response.headers['content-type'] = 'text/html; charset=UTF-8'
241 242 return pyramid_response
242 243
243 244
244 245 def error_handler(exception, request):
245 246 # TODO: dan: replace the old pylons error controller with this
246 247 from rhodecode.model.settings import SettingsModel
247 248 from rhodecode.lib.utils2 import AttributeDict
248 249
249 250 try:
250 251 rc_config = SettingsModel().get_all_settings()
251 252 except Exception:
252 253 log.exception('failed to fetch settings')
253 254 rc_config = {}
254 255
255 256 base_response = HTTPInternalServerError()
256 257 # prefer original exception for the response since it may have headers set
257 258 if isinstance(exception, HTTPError):
258 259 base_response = exception
259 260
260 261 c = AttributeDict()
261 262 c.error_message = base_response.status
262 263 c.error_explanation = base_response.explanation or str(base_response)
263 264 c.visual = AttributeDict()
264 265
265 266 c.visual.rhodecode_support_url = (
266 267 request.registry.settings.get('rhodecode_support_url') or
267 268 request.route_url('rhodecode_support')
268 269 )
269 270 c.redirect_time = 0
270 271 c.rhodecode_name = rc_config.get('rhodecode_title', '')
271 272 if not c.rhodecode_name:
272 273 c.rhodecode_name = 'Rhodecode'
273 274
274 275 response = render_to_response(
275 276 '/errors/error_document.html', {'c': c}, request=request,
276 277 response=base_response)
277 278
278 279 return response
279 280
280 281
281 282 def includeme(config):
282 283 settings = config.registry.settings
283 284
284 285 # plugin information
285 286 config.registry.rhodecode_plugins = OrderedDict()
286 287
287 288 config.add_directive(
288 289 'register_rhodecode_plugin', register_rhodecode_plugin)
289 290
290 291 if asbool(settings.get('appenlight', 'false')):
291 292 config.include('appenlight_client.ext.pyramid_tween')
292 293
293 294 # Includes which are required. The application would fail without them.
294 295 config.include('pyramid_mako')
295 296 config.include('pyramid_beaker')
296 297 config.include('rhodecode.channelstream')
297 298 config.include('rhodecode.admin')
298 299 config.include('rhodecode.authentication')
299 300 config.include('rhodecode.integrations')
300 301 config.include('rhodecode.login')
301 302 config.include('rhodecode.tweens')
302 303 config.include('rhodecode.api')
303 304 config.include('rhodecode.svn_support')
304 305 config.add_route(
305 306 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
306 307
307 308 # Add subscribers.
308 309 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
309 310
310 311 # Set the authorization policy.
311 312 authz_policy = ACLAuthorizationPolicy()
312 313 config.set_authorization_policy(authz_policy)
313 314
314 315 # Set the default renderer for HTML templates to mako.
315 316 config.add_mako_renderer('.html')
316 317
317 318 # include RhodeCode plugins
318 319 includes = aslist(settings.get('rhodecode.includes', []))
319 320 for inc in includes:
320 321 config.include(inc)
321 322
322 323 # This is the glue which allows us to migrate in chunks. By registering the
323 324 # pylons based application as the "Not Found" view in Pyramid, we will
324 325 # fallback to the old application each time the new one does not yet know
325 326 # how to handle a request.
326 327 config.add_notfound_view(make_not_found_view(config))
327 328
328 329 if not settings.get('debugtoolbar.enabled', False):
329 330 # if no toolbar, then any exception gets caught and rendered
330 331 config.add_view(error_handler, context=Exception)
331 332
332 333 config.add_view(error_handler, context=HTTPError)
333 334
334 335
335 336 def includeme_first(config):
336 337 # redirect automatic browser favicon.ico requests to correct place
337 338 def favicon_redirect(context, request):
338 339 return redirect(
339 340 request.static_path('rhodecode:public/images/favicon.ico'))
340 341
341 342 config.add_view(favicon_redirect, route_name='favicon')
342 343 config.add_route('favicon', '/favicon.ico')
343 344
344 345 config.add_static_view(
345 346 '_static/deform', 'deform:static')
346 347 config.add_static_view(
347 348 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
348 349
349 350
350 351 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
351 352 """
352 353 Apply outer WSGI middlewares around the application.
353 354
354 355 Part of this has been moved up from the Pylons layer, so that the
355 356 data is also available if old Pylons code is hit through an already ported
356 357 view.
357 358 """
358 359 settings = config.registry.settings
359 360
360 361 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
361 362 pyramid_app = HttpsFixup(pyramid_app, settings)
362 363
363 364 # Add RoutesMiddleware to support the pylons compatibility tween during
364 365 # migration to pyramid.
365 366 pyramid_app = SkippableRoutesMiddleware(
366 367 pyramid_app, config.registry._pylons_compat_config['routes.map'],
367 368 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
368 369
369 370 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
370 371
371 372 if settings['gzip_responses']:
372 373 pyramid_app = make_gzip_middleware(
373 374 pyramid_app, settings, compress_level=1)
374 375
375 376 return pyramid_app
376 377
377 378
378 379 def sanitize_settings_and_apply_defaults(settings):
379 380 """
380 381 Applies settings defaults and does all type conversion.
381 382
382 383 We would move all settings parsing and preparation into this place, so that
383 384 we have only one place left which deals with this part. The remaining parts
384 385 of the application would start to rely fully on well prepared settings.
385 386
386 387 This piece would later be split up per topic to avoid a big fat monster
387 388 function.
388 389 """
389 390
390 391 # Pyramid's mako renderer has to search in the templates folder so that the
391 392 # old templates still work. Ported and new templates are expected to use
392 393 # real asset specifications for the includes.
393 394 mako_directories = settings.setdefault('mako.directories', [
394 395 # Base templates of the original Pylons application
395 396 'rhodecode:templates',
396 397 ])
397 398 log.debug(
398 399 "Using the following Mako template directories: %s",
399 400 mako_directories)
400 401
401 402 # Default includes, possible to change as a user
402 403 pyramid_includes = settings.setdefault('pyramid.includes', [
403 404 'rhodecode.lib.middleware.request_wrapper',
404 405 ])
405 406 log.debug(
406 407 "Using the following pyramid.includes: %s",
407 408 pyramid_includes)
408 409
409 410 # TODO: johbo: Re-think this, usually the call to config.include
410 411 # should allow to pass in a prefix.
411 412 settings.setdefault('rhodecode.api.url', '/_admin/api')
412 413
413 414 # Sanitize generic settings.
414 415 _list_setting(settings, 'default_encoding', 'UTF-8')
415 416 _bool_setting(settings, 'is_test', 'false')
416 417 _bool_setting(settings, 'gzip_responses', 'false')
417 418
418 419 # Call split out functions that sanitize settings for each topic.
419 420 _sanitize_appenlight_settings(settings)
420 421 _sanitize_vcs_settings(settings)
421 422
422 423 return settings
423 424
424 425
425 426 def _sanitize_appenlight_settings(settings):
426 427 _bool_setting(settings, 'appenlight', 'false')
427 428
428 429
429 430 def _sanitize_vcs_settings(settings):
430 431 """
431 432 Applies settings defaults and does type conversion for all VCS related
432 433 settings.
433 434 """
434 435 _string_setting(settings, 'vcs.svn.compatible_version', '')
435 436 _string_setting(settings, 'git_rev_filter', '--all')
436 437 _string_setting(settings, 'vcs.hooks.protocol', 'pyro4')
437 438 _string_setting(settings, 'vcs.server', '')
438 439 _string_setting(settings, 'vcs.server.log_level', 'debug')
439 440 _string_setting(settings, 'vcs.server.protocol', 'pyro4')
440 441 _bool_setting(settings, 'startup.import_repos', 'false')
441 442 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
442 443 _bool_setting(settings, 'vcs.server.enable', 'true')
443 444 _bool_setting(settings, 'vcs.start_server', 'false')
444 445 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
445 446
446 447
447 448 def _bool_setting(settings, name, default):
448 449 settings[name] = asbool(settings.get(name, default))
449 450
450 451
451 452 def _list_setting(settings, name, default):
452 453 raw_value = settings.get(name, default)
453 454
454 # Check if we get a setting with the old syntax (comma separated).
455 if ',' in raw_value:
456 raw_value = raw_value.replace(',', ' ')
457
455 old_separator = ','
456 if old_separator in raw_value:
457 # If we get a comma separated list, pass it to our own function.
458 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
459 else:
460 # Otherwise we assume it uses pyramids space/newline separation.
458 461 settings[name] = aslist(raw_value)
459 462
460 463
461 464 def _string_setting(settings, name, default):
462 465 settings[name] = settings.get(name, default).lower()
General Comments 0
You need to be logged in to leave comments. Login now