##// END OF EJS Templates
configuration: Allows to use format style "{ENV_NAME}" placeholders in the configuration.
marcink -
r2818:4e04a411 default
parent child Browse files
Show More
@@ -1,440 +1,460 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 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 import os
21 22 import logging
22 23 import traceback
23 24 import collections
24 25
25 26 from paste.gzipper import make_gzip_middleware
26 27 from pyramid.wsgi import wsgiapp
27 28 from pyramid.authorization import ACLAuthorizationPolicy
28 29 from pyramid.config import Configurator
29 30 from pyramid.settings import asbool, aslist
30 31 from pyramid.httpexceptions import (
31 32 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
32 33 from pyramid.events import ApplicationCreated
33 34 from pyramid.renderers import render_to_response
34 35
35 36 from rhodecode.model import meta
36 37 from rhodecode.config import patches
37 38 from rhodecode.config import utils as config_utils
38 39 from rhodecode.config.environment import load_pyramid_environment
39 40
40 41 from rhodecode.lib.middleware.vcs import VCSMiddleware
41 42 from rhodecode.lib.request import Request
42 43 from rhodecode.lib.vcs import VCSCommunicationError
43 44 from rhodecode.lib.exceptions import VCSServerUnavailable
44 45 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
45 46 from rhodecode.lib.middleware.https_fixup import HttpsFixup
46 47 from rhodecode.lib.celerylib.loader import configure_celery
47 48 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
48 49 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
49 50 from rhodecode.subscribers import (
50 51 scan_repositories_if_enabled, write_js_routes_if_enabled,
51 52 write_metadata_if_needed, inject_app_settings)
52 53
53 54
54 55 log = logging.getLogger(__name__)
55 56
56 57
57 58 def is_http_error(response):
58 59 # error which should have traceback
59 60 return response.status_code > 499
60 61
61 62
62 63 def make_pyramid_app(global_config, **settings):
63 64 """
64 65 Constructs the WSGI application based on Pyramid.
65 66
66 67 Specials:
67 68
68 69 * The application can also be integrated like a plugin via the call to
69 70 `includeme`. This is accompanied with the other utility functions which
70 71 are called. Changing this should be done with great care to not break
71 72 cases when these fragments are assembled from another place.
72 73
73 74 """
75
76 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
77 # will be replaced by the value of the environment variable "NAME" in this case.
78 environ = {
79 'ENV_{}'.format(key): value for key, value in os.environ.items()}
80
81 global_config = _substitute_values(global_config, environ)
82 settings = _substitute_values(settings, environ)
83
74 84 sanitize_settings_and_apply_defaults(settings)
75 85
76 86 config = Configurator(settings=settings)
77 87
78 88 # Apply compatibility patches
79 89 patches.inspect_getargspec()
80 90
81 91 load_pyramid_environment(global_config, settings)
82 92
83 93 # Static file view comes first
84 94 includeme_first(config)
85 95
86 96 includeme(config)
87 97
88 98 pyramid_app = config.make_wsgi_app()
89 99 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
90 100 pyramid_app.config = config
91 101
92 102 config.configure_celery(global_config['__file__'])
93 103 # creating the app uses a connection - return it after we are done
94 104 meta.Session.remove()
95 105
96 106 log.info('Pyramid app %s created and configured.', pyramid_app)
97 107 return pyramid_app
98 108
99 109
100 110 def not_found_view(request):
101 111 """
102 112 This creates the view which should be registered as not-found-view to
103 113 pyramid.
104 114 """
105 115
106 116 if not getattr(request, 'vcs_call', None):
107 117 # handle like regular case with our error_handler
108 118 return error_handler(HTTPNotFound(), request)
109 119
110 120 # handle not found view as a vcs call
111 121 settings = request.registry.settings
112 122 ae_client = getattr(request, 'ae_client', None)
113 123 vcs_app = VCSMiddleware(
114 124 HTTPNotFound(), request.registry, settings,
115 125 appenlight_client=ae_client)
116 126
117 127 return wsgiapp(vcs_app)(None, request)
118 128
119 129
120 130 def error_handler(exception, request):
121 131 import rhodecode
122 132 from rhodecode.lib import helpers
123 133
124 134 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
125 135
126 136 base_response = HTTPInternalServerError()
127 137 # prefer original exception for the response since it may have headers set
128 138 if isinstance(exception, HTTPException):
129 139 base_response = exception
130 140 elif isinstance(exception, VCSCommunicationError):
131 141 base_response = VCSServerUnavailable()
132 142
133 143 if is_http_error(base_response):
134 144 log.exception(
135 145 'error occurred handling this request for path: %s', request.path)
136 146
137 147 error_explanation = base_response.explanation or str(base_response)
138 148 if base_response.status_code == 404:
139 149 error_explanation += " Or you don't have permission to access it."
140 150 c = AttributeDict()
141 151 c.error_message = base_response.status
142 152 c.error_explanation = error_explanation
143 153 c.visual = AttributeDict()
144 154
145 155 c.visual.rhodecode_support_url = (
146 156 request.registry.settings.get('rhodecode_support_url') or
147 157 request.route_url('rhodecode_support')
148 158 )
149 159 c.redirect_time = 0
150 160 c.rhodecode_name = rhodecode_title
151 161 if not c.rhodecode_name:
152 162 c.rhodecode_name = 'Rhodecode'
153 163
154 164 c.causes = []
155 165 if is_http_error(base_response):
156 166 c.causes.append('Server is overloaded.')
157 167 c.causes.append('Server database connection is lost.')
158 168 c.causes.append('Server expected unhandled error.')
159 169
160 170 if hasattr(base_response, 'causes'):
161 171 c.causes = base_response.causes
162 172
163 173 c.messages = helpers.flash.pop_messages(request=request)
164 174 c.traceback = traceback.format_exc()
165 175 response = render_to_response(
166 176 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
167 177 response=base_response)
168 178
169 179 return response
170 180
171 181
172 182 def includeme_first(config):
173 183 # redirect automatic browser favicon.ico requests to correct place
174 184 def favicon_redirect(context, request):
175 185 return HTTPFound(
176 186 request.static_path('rhodecode:public/images/favicon.ico'))
177 187
178 188 config.add_view(favicon_redirect, route_name='favicon')
179 189 config.add_route('favicon', '/favicon.ico')
180 190
181 191 def robots_redirect(context, request):
182 192 return HTTPFound(
183 193 request.static_path('rhodecode:public/robots.txt'))
184 194
185 195 config.add_view(robots_redirect, route_name='robots')
186 196 config.add_route('robots', '/robots.txt')
187 197
188 198 config.add_static_view(
189 199 '_static/deform', 'deform:static')
190 200 config.add_static_view(
191 201 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
192 202
193 203
194 204 def includeme(config):
195 205 settings = config.registry.settings
196 206 config.set_request_factory(Request)
197 207
198 208 # plugin information
199 209 config.registry.rhodecode_plugins = collections.OrderedDict()
200 210
201 211 config.add_directive(
202 212 'register_rhodecode_plugin', register_rhodecode_plugin)
203 213
204 214 config.add_directive('configure_celery', configure_celery)
205 215
206 216 if asbool(settings.get('appenlight', 'false')):
207 217 config.include('appenlight_client.ext.pyramid_tween')
208 218
209 219 # Includes which are required. The application would fail without them.
210 220 config.include('pyramid_mako')
211 221 config.include('pyramid_beaker')
212 222 config.include('rhodecode.lib.caches')
213 223
214 224 config.include('rhodecode.authentication')
215 225 config.include('rhodecode.integrations')
216 226
217 227 # apps
218 228 config.include('rhodecode.apps._base')
219 229 config.include('rhodecode.apps.ops')
220 230
221 231 config.include('rhodecode.apps.admin')
222 232 config.include('rhodecode.apps.channelstream')
223 233 config.include('rhodecode.apps.login')
224 234 config.include('rhodecode.apps.home')
225 235 config.include('rhodecode.apps.journal')
226 236 config.include('rhodecode.apps.repository')
227 237 config.include('rhodecode.apps.repo_group')
228 238 config.include('rhodecode.apps.user_group')
229 239 config.include('rhodecode.apps.search')
230 240 config.include('rhodecode.apps.user_profile')
231 241 config.include('rhodecode.apps.user_group_profile')
232 242 config.include('rhodecode.apps.my_account')
233 243 config.include('rhodecode.apps.svn_support')
234 244 config.include('rhodecode.apps.ssh_support')
235 245 config.include('rhodecode.apps.gist')
236 246
237 247 config.include('rhodecode.apps.debug_style')
238 248 config.include('rhodecode.tweens')
239 249 config.include('rhodecode.api')
240 250
241 251 config.add_route(
242 252 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
243 253
244 254 config.add_translation_dirs('rhodecode:i18n/')
245 255 settings['default_locale_name'] = settings.get('lang', 'en')
246 256
247 257 # Add subscribers.
248 258 config.add_subscriber(inject_app_settings, ApplicationCreated)
249 259 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
250 260 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
251 261 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
252 262
253 263 # events
254 264 # TODO(marcink): this should be done when pyramid migration is finished
255 265 # config.add_subscriber(
256 266 # 'rhodecode.integrations.integrations_event_handler',
257 267 # 'rhodecode.events.RhodecodeEvent')
258 268
259 269 # request custom methods
260 270 config.add_request_method(
261 271 'rhodecode.lib.partial_renderer.get_partial_renderer',
262 272 'get_partial_renderer')
263 273
264 274 # Set the authorization policy.
265 275 authz_policy = ACLAuthorizationPolicy()
266 276 config.set_authorization_policy(authz_policy)
267 277
268 278 # Set the default renderer for HTML templates to mako.
269 279 config.add_mako_renderer('.html')
270 280
271 281 config.add_renderer(
272 282 name='json_ext',
273 283 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
274 284
275 285 # include RhodeCode plugins
276 286 includes = aslist(settings.get('rhodecode.includes', []))
277 287 for inc in includes:
278 288 config.include(inc)
279 289
280 290 # custom not found view, if our pyramid app doesn't know how to handle
281 291 # the request pass it to potential VCS handling ap
282 292 config.add_notfound_view(not_found_view)
283 293 if not settings.get('debugtoolbar.enabled', False):
284 294 # disabled debugtoolbar handle all exceptions via the error_handlers
285 295 config.add_view(error_handler, context=Exception)
286 296
287 297 # all errors including 403/404/50X
288 298 config.add_view(error_handler, context=HTTPError)
289 299
290 300
291 301 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
292 302 """
293 303 Apply outer WSGI middlewares around the application.
294 304 """
295 305 settings = config.registry.settings
296 306
297 307 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
298 308 pyramid_app = HttpsFixup(pyramid_app, settings)
299 309
300 310 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
301 311 pyramid_app, settings)
302 312 config.registry.ae_client = _ae_client
303 313
304 314 if settings['gzip_responses']:
305 315 pyramid_app = make_gzip_middleware(
306 316 pyramid_app, settings, compress_level=1)
307 317
308 318 # this should be the outer most middleware in the wsgi stack since
309 319 # middleware like Routes make database calls
310 320 def pyramid_app_with_cleanup(environ, start_response):
311 321 try:
312 322 return pyramid_app(environ, start_response)
313 323 finally:
314 324 # Dispose current database session and rollback uncommitted
315 325 # transactions.
316 326 meta.Session.remove()
317 327
318 328 # In a single threaded mode server, on non sqlite db we should have
319 329 # '0 Current Checked out connections' at the end of a request,
320 330 # if not, then something, somewhere is leaving a connection open
321 331 pool = meta.Base.metadata.bind.engine.pool
322 332 log.debug('sa pool status: %s', pool.status())
323 333
324 334 return pyramid_app_with_cleanup
325 335
326 336
327 337 def sanitize_settings_and_apply_defaults(settings):
328 338 """
329 339 Applies settings defaults and does all type conversion.
330 340
331 341 We would move all settings parsing and preparation into this place, so that
332 342 we have only one place left which deals with this part. The remaining parts
333 343 of the application would start to rely fully on well prepared settings.
334 344
335 345 This piece would later be split up per topic to avoid a big fat monster
336 346 function.
337 347 """
338 348
339 349 settings.setdefault('rhodecode.edition', 'Community Edition')
340 350
341 351 if 'mako.default_filters' not in settings:
342 352 # set custom default filters if we don't have it defined
343 353 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
344 354 settings['mako.default_filters'] = 'h_filter'
345 355
346 356 if 'mako.directories' not in settings:
347 357 mako_directories = settings.setdefault('mako.directories', [
348 358 # Base templates of the original application
349 359 'rhodecode:templates',
350 360 ])
351 361 log.debug(
352 362 "Using the following Mako template directories: %s",
353 363 mako_directories)
354 364
355 365 # Default includes, possible to change as a user
356 366 pyramid_includes = settings.setdefault('pyramid.includes', [
357 367 'rhodecode.lib.middleware.request_wrapper',
358 368 ])
359 369 log.debug(
360 370 "Using the following pyramid.includes: %s",
361 371 pyramid_includes)
362 372
363 373 # TODO: johbo: Re-think this, usually the call to config.include
364 374 # should allow to pass in a prefix.
365 375 settings.setdefault('rhodecode.api.url', '/_admin/api')
366 376
367 377 # Sanitize generic settings.
368 378 _list_setting(settings, 'default_encoding', 'UTF-8')
369 379 _bool_setting(settings, 'is_test', 'false')
370 380 _bool_setting(settings, 'gzip_responses', 'false')
371 381
372 382 # Call split out functions that sanitize settings for each topic.
373 383 _sanitize_appenlight_settings(settings)
374 384 _sanitize_vcs_settings(settings)
375 385
376 386 # configure instance id
377 387 config_utils.set_instance_id(settings)
378 388
379 389 return settings
380 390
381 391
382 392 def _sanitize_appenlight_settings(settings):
383 393 _bool_setting(settings, 'appenlight', 'false')
384 394
385 395
386 396 def _sanitize_vcs_settings(settings):
387 397 """
388 398 Applies settings defaults and does type conversion for all VCS related
389 399 settings.
390 400 """
391 401 _string_setting(settings, 'vcs.svn.compatible_version', '')
392 402 _string_setting(settings, 'git_rev_filter', '--all')
393 403 _string_setting(settings, 'vcs.hooks.protocol', 'http')
394 404 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
395 405 _string_setting(settings, 'vcs.server', '')
396 406 _string_setting(settings, 'vcs.server.log_level', 'debug')
397 407 _string_setting(settings, 'vcs.server.protocol', 'http')
398 408 _bool_setting(settings, 'startup.import_repos', 'false')
399 409 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
400 410 _bool_setting(settings, 'vcs.server.enable', 'true')
401 411 _bool_setting(settings, 'vcs.start_server', 'false')
402 412 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
403 413 _int_setting(settings, 'vcs.connection_timeout', 3600)
404 414
405 415 # Support legacy values of vcs.scm_app_implementation. Legacy
406 416 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
407 417 # which is now mapped to 'http'.
408 418 scm_app_impl = settings['vcs.scm_app_implementation']
409 419 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
410 420 settings['vcs.scm_app_implementation'] = 'http'
411 421
412 422
413 423 def _int_setting(settings, name, default):
414 424 settings[name] = int(settings.get(name, default))
415 425
416 426
417 427 def _bool_setting(settings, name, default):
418 428 input_val = settings.get(name, default)
419 429 if isinstance(input_val, unicode):
420 430 input_val = input_val.encode('utf8')
421 431 settings[name] = asbool(input_val)
422 432
423 433
424 434 def _list_setting(settings, name, default):
425 435 raw_value = settings.get(name, default)
426 436
427 437 old_separator = ','
428 438 if old_separator in raw_value:
429 439 # If we get a comma separated list, pass it to our own function.
430 440 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
431 441 else:
432 442 # Otherwise we assume it uses pyramids space/newline separation.
433 443 settings[name] = aslist(raw_value)
434 444
435 445
436 446 def _string_setting(settings, name, default, lower=True):
437 447 value = settings.get(name, default)
438 448 if lower:
439 449 value = value.lower()
440 450 settings[name] = value
451
452
453 def _substitute_values(mapping, substitutions):
454 result = {
455 # Note: Cannot use regular replacements, since they would clash
456 # with the implementation of ConfigParser. Using "format" instead.
457 key: value.format(**substitutions)
458 for key, value in mapping.items()
459 }
460 return result
General Comments 0
You need to be logged in to leave comments. Login now