##// END OF EJS Templates
core: added metadata for control upgrades.
marcink -
r1392:970f8533 default
parent child Browse files
Show More
@@ -1,497 +1,499 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 (
35 35 HTTPError, HTTPInternalServerError, HTTPFound)
36 36 from pyramid.events import ApplicationCreated
37 37 from pyramid.renderers import render_to_response
38 38 from routes.middleware import RoutesMiddleware
39 39 import routes.util
40 40
41 41 import rhodecode
42 42 from rhodecode.model import meta
43 43 from rhodecode.config import patches
44 44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 45 from rhodecode.config.environment import (
46 46 load_environment, load_pyramid_environment)
47 47 from rhodecode.lib.middleware import csrf
48 48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 49 from rhodecode.lib.middleware.error_handling import (
50 50 PylonsErrorHandlingMiddleware)
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 54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
55 from rhodecode.subscribers import scan_repositories_if_enabled
55 from rhodecode.subscribers import (
56 scan_repositories_if_enabled, write_metadata_if_needed)
56 57
57 58
58 59 log = logging.getLogger(__name__)
59 60
60 61
61 62 # this is used to avoid avoid the route lookup overhead in routesmiddleware
62 63 # for certain routes which won't go to pylons to - eg. static files, debugger
63 64 # it is only needed for the pylons migration and can be removed once complete
64 65 class SkippableRoutesMiddleware(RoutesMiddleware):
65 66 """ Routes middleware that allows you to skip prefixes """
66 67
67 68 def __init__(self, *args, **kw):
68 69 self.skip_prefixes = kw.pop('skip_prefixes', [])
69 70 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
70 71
71 72 def __call__(self, environ, start_response):
72 73 for prefix in self.skip_prefixes:
73 74 if environ['PATH_INFO'].startswith(prefix):
74 75 # added to avoid the case when a missing /_static route falls
75 76 # through to pylons and causes an exception as pylons is
76 77 # expecting wsgiorg.routingargs to be set in the environ
77 78 # by RoutesMiddleware.
78 79 if 'wsgiorg.routing_args' not in environ:
79 80 environ['wsgiorg.routing_args'] = (None, {})
80 81 return self.app(environ, start_response)
81 82
82 83 return super(SkippableRoutesMiddleware, self).__call__(
83 84 environ, start_response)
84 85
85 86
86 87 def make_app(global_conf, static_files=True, **app_conf):
87 88 """Create a Pylons WSGI application and return it
88 89
89 90 ``global_conf``
90 91 The inherited configuration for this application. Normally from
91 92 the [DEFAULT] section of the Paste ini file.
92 93
93 94 ``app_conf``
94 95 The application's local configuration. Normally specified in
95 96 the [app:<name>] section of the Paste ini file (where <name>
96 97 defaults to main).
97 98
98 99 """
99 100 # Apply compatibility patches
100 101 patches.kombu_1_5_1_python_2_7_11()
101 102 patches.inspect_getargspec()
102 103
103 104 # Configure the Pylons environment
104 105 config = load_environment(global_conf, app_conf)
105 106
106 107 # The Pylons WSGI app
107 108 app = PylonsApp(config=config)
108 109 if rhodecode.is_test:
109 110 app = csrf.CSRFDetector(app)
110 111
111 112 expected_origin = config.get('expected_origin')
112 113 if expected_origin:
113 114 # The API can be accessed from other Origins.
114 115 app = csrf.OriginChecker(app, expected_origin,
115 116 skip_urls=[routes.util.url_for('api')])
116 117
117 118 # Establish the Registry for this application
118 119 app = RegistryManager(app)
119 120
120 121 app.config = config
121 122
122 123 return app
123 124
124 125
125 126 def make_pyramid_app(global_config, **settings):
126 127 """
127 128 Constructs the WSGI application based on Pyramid and wraps the Pylons based
128 129 application.
129 130
130 131 Specials:
131 132
132 133 * We migrate from Pylons to Pyramid. While doing this, we keep both
133 134 frameworks functional. This involves moving some WSGI middlewares around
134 135 and providing access to some data internals, so that the old code is
135 136 still functional.
136 137
137 138 * The application can also be integrated like a plugin via the call to
138 139 `includeme`. This is accompanied with the other utility functions which
139 140 are called. Changing this should be done with great care to not break
140 141 cases when these fragments are assembled from another place.
141 142
142 143 """
143 144 # The edition string should be available in pylons too, so we add it here
144 145 # before copying the settings.
145 146 settings.setdefault('rhodecode.edition', 'Community Edition')
146 147
147 148 # As long as our Pylons application does expect "unprepared" settings, make
148 149 # sure that we keep an unmodified copy. This avoids unintentional change of
149 150 # behavior in the old application.
150 151 settings_pylons = settings.copy()
151 152
152 153 sanitize_settings_and_apply_defaults(settings)
153 154 config = Configurator(settings=settings)
154 155 add_pylons_compat_data(config.registry, global_config, settings_pylons)
155 156
156 157 load_pyramid_environment(global_config, settings)
157 158
158 159 includeme_first(config)
159 160 includeme(config)
160 161 pyramid_app = config.make_wsgi_app()
161 162 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
162 163 pyramid_app.config = config
163 164
164 165 # creating the app uses a connection - return it after we are done
165 166 meta.Session.remove()
166 167
167 168 return pyramid_app
168 169
169 170
170 171 def make_not_found_view(config):
171 172 """
172 173 This creates the view which should be registered as not-found-view to
173 174 pyramid. Basically it contains of the old pylons app, converted to a view.
174 175 Additionally it is wrapped by some other middlewares.
175 176 """
176 177 settings = config.registry.settings
177 178 vcs_server_enabled = settings['vcs.server.enable']
178 179
179 180 # Make pylons app from unprepared settings.
180 181 pylons_app = make_app(
181 182 config.registry._pylons_compat_global_config,
182 183 **config.registry._pylons_compat_settings)
183 184 config.registry._pylons_compat_config = pylons_app.config
184 185
185 186 # Appenlight monitoring.
186 187 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
187 188 pylons_app, settings)
188 189
189 190 # The pylons app is executed inside of the pyramid 404 exception handler.
190 191 # Exceptions which are raised inside of it are not handled by pyramid
191 192 # again. Therefore we add a middleware that invokes the error handler in
192 193 # case of an exception or error response. This way we return proper error
193 194 # HTML pages in case of an error.
194 195 reraise = (settings.get('debugtoolbar.enabled', False) or
195 196 rhodecode.disable_error_handler)
196 197 pylons_app = PylonsErrorHandlingMiddleware(
197 198 pylons_app, error_handler, reraise)
198 199
199 200 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
200 201 # view to handle the request. Therefore it is wrapped around the pylons
201 202 # app. It has to be outside of the error handling otherwise error responses
202 203 # from the vcsserver are converted to HTML error pages. This confuses the
203 204 # command line tools and the user won't get a meaningful error message.
204 205 if vcs_server_enabled:
205 206 pylons_app = VCSMiddleware(
206 207 pylons_app, settings, appenlight_client, registry=config.registry)
207 208
208 209 # Convert WSGI app to pyramid view and return it.
209 210 return wsgiapp(pylons_app)
210 211
211 212
212 213 def add_pylons_compat_data(registry, global_config, settings):
213 214 """
214 215 Attach data to the registry to support the Pylons integration.
215 216 """
216 217 registry._pylons_compat_global_config = global_config
217 218 registry._pylons_compat_settings = settings
218 219
219 220
220 221 def error_handler(exception, request):
221 222 from rhodecode.model.settings import SettingsModel
222 223 from rhodecode.lib.utils2 import AttributeDict
223 224
224 225 try:
225 226 rc_config = SettingsModel().get_all_settings()
226 227 except Exception:
227 228 log.exception('failed to fetch settings')
228 229 rc_config = {}
229 230
230 231 base_response = HTTPInternalServerError()
231 232 # prefer original exception for the response since it may have headers set
232 233 if isinstance(exception, HTTPError):
233 234 base_response = exception
234 235
235 236 def is_http_error(response):
236 237 # error which should have traceback
237 238 return response.status_code > 499
238 239
239 240 if is_http_error(base_response):
240 241 log.exception(
241 242 'error occurred handling this request for path: %s', request.path)
242 243
243 244 c = AttributeDict()
244 245 c.error_message = base_response.status
245 246 c.error_explanation = base_response.explanation or str(base_response)
246 247 c.visual = AttributeDict()
247 248
248 249 c.visual.rhodecode_support_url = (
249 250 request.registry.settings.get('rhodecode_support_url') or
250 251 request.route_url('rhodecode_support')
251 252 )
252 253 c.redirect_time = 0
253 254 c.rhodecode_name = rc_config.get('rhodecode_title', '')
254 255 if not c.rhodecode_name:
255 256 c.rhodecode_name = 'Rhodecode'
256 257
257 258 c.causes = []
258 259 if hasattr(base_response, 'causes'):
259 260 c.causes = base_response.causes
260 261
261 262 response = render_to_response(
262 263 '/errors/error_document.mako', {'c': c}, request=request,
263 264 response=base_response)
264 265
265 266 return response
266 267
267 268
268 269 def includeme(config):
269 270 settings = config.registry.settings
270 271
271 272 # plugin information
272 273 config.registry.rhodecode_plugins = OrderedDict()
273 274
274 275 config.add_directive(
275 276 'register_rhodecode_plugin', register_rhodecode_plugin)
276 277
277 278 if asbool(settings.get('appenlight', 'false')):
278 279 config.include('appenlight_client.ext.pyramid_tween')
279 280
280 281 # Includes which are required. The application would fail without them.
281 282 config.include('pyramid_mako')
282 283 config.include('pyramid_beaker')
283 284 config.include('rhodecode.channelstream')
284 285 config.include('rhodecode.admin')
285 286 config.include('rhodecode.authentication')
286 287 config.include('rhodecode.integrations')
287 288 config.include('rhodecode.login')
288 289 config.include('rhodecode.tweens')
289 290 config.include('rhodecode.api')
290 291 config.include('rhodecode.svn_support')
291 292 config.add_route(
292 293 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
293 294
294 295 config.add_translation_dirs('rhodecode:i18n/')
295 296 settings['default_locale_name'] = settings.get('lang', 'en')
296 297
297 298 # Add subscribers.
298 299 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
300 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
299 301
300 302 # Set the authorization policy.
301 303 authz_policy = ACLAuthorizationPolicy()
302 304 config.set_authorization_policy(authz_policy)
303 305
304 306 # Set the default renderer for HTML templates to mako.
305 307 config.add_mako_renderer('.html')
306 308
307 309 # include RhodeCode plugins
308 310 includes = aslist(settings.get('rhodecode.includes', []))
309 311 for inc in includes:
310 312 config.include(inc)
311 313
312 314 # This is the glue which allows us to migrate in chunks. By registering the
313 315 # pylons based application as the "Not Found" view in Pyramid, we will
314 316 # fallback to the old application each time the new one does not yet know
315 317 # how to handle a request.
316 318 config.add_notfound_view(make_not_found_view(config))
317 319
318 320 if not settings.get('debugtoolbar.enabled', False):
319 321 # if no toolbar, then any exception gets caught and rendered
320 322 config.add_view(error_handler, context=Exception)
321 323
322 324 config.add_view(error_handler, context=HTTPError)
323 325
324 326
325 327 def includeme_first(config):
326 328 # redirect automatic browser favicon.ico requests to correct place
327 329 def favicon_redirect(context, request):
328 330 return HTTPFound(
329 331 request.static_path('rhodecode:public/images/favicon.ico'))
330 332
331 333 config.add_view(favicon_redirect, route_name='favicon')
332 334 config.add_route('favicon', '/favicon.ico')
333 335
334 336 def robots_redirect(context, request):
335 337 return HTTPFound(
336 338 request.static_path('rhodecode:public/robots.txt'))
337 339
338 340 config.add_view(robots_redirect, route_name='robots')
339 341 config.add_route('robots', '/robots.txt')
340 342
341 343 config.add_static_view(
342 344 '_static/deform', 'deform:static')
343 345 config.add_static_view(
344 346 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
345 347
346 348
347 349 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
348 350 """
349 351 Apply outer WSGI middlewares around the application.
350 352
351 353 Part of this has been moved up from the Pylons layer, so that the
352 354 data is also available if old Pylons code is hit through an already ported
353 355 view.
354 356 """
355 357 settings = config.registry.settings
356 358
357 359 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
358 360 pyramid_app = HttpsFixup(pyramid_app, settings)
359 361
360 362 # Add RoutesMiddleware to support the pylons compatibility tween during
361 363 # migration to pyramid.
362 364 pyramid_app = SkippableRoutesMiddleware(
363 365 pyramid_app, config.registry._pylons_compat_config['routes.map'],
364 366 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
365 367
366 368 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
367 369
368 370 if settings['gzip_responses']:
369 371 pyramid_app = make_gzip_middleware(
370 372 pyramid_app, settings, compress_level=1)
371 373
372 374 # this should be the outer most middleware in the wsgi stack since
373 375 # middleware like Routes make database calls
374 376 def pyramid_app_with_cleanup(environ, start_response):
375 377 try:
376 378 return pyramid_app(environ, start_response)
377 379 finally:
378 380 # Dispose current database session and rollback uncommitted
379 381 # transactions.
380 382 meta.Session.remove()
381 383
382 384 # In a single threaded mode server, on non sqlite db we should have
383 385 # '0 Current Checked out connections' at the end of a request,
384 386 # if not, then something, somewhere is leaving a connection open
385 387 pool = meta.Base.metadata.bind.engine.pool
386 388 log.debug('sa pool status: %s', pool.status())
387 389
388 390
389 391 return pyramid_app_with_cleanup
390 392
391 393
392 394 def sanitize_settings_and_apply_defaults(settings):
393 395 """
394 396 Applies settings defaults and does all type conversion.
395 397
396 398 We would move all settings parsing and preparation into this place, so that
397 399 we have only one place left which deals with this part. The remaining parts
398 400 of the application would start to rely fully on well prepared settings.
399 401
400 402 This piece would later be split up per topic to avoid a big fat monster
401 403 function.
402 404 """
403 405
404 406 # Pyramid's mako renderer has to search in the templates folder so that the
405 407 # old templates still work. Ported and new templates are expected to use
406 408 # real asset specifications for the includes.
407 409 mako_directories = settings.setdefault('mako.directories', [
408 410 # Base templates of the original Pylons application
409 411 'rhodecode:templates',
410 412 ])
411 413 log.debug(
412 414 "Using the following Mako template directories: %s",
413 415 mako_directories)
414 416
415 417 # Default includes, possible to change as a user
416 418 pyramid_includes = settings.setdefault('pyramid.includes', [
417 419 'rhodecode.lib.middleware.request_wrapper',
418 420 ])
419 421 log.debug(
420 422 "Using the following pyramid.includes: %s",
421 423 pyramid_includes)
422 424
423 425 # TODO: johbo: Re-think this, usually the call to config.include
424 426 # should allow to pass in a prefix.
425 427 settings.setdefault('rhodecode.api.url', '/_admin/api')
426 428
427 429 # Sanitize generic settings.
428 430 _list_setting(settings, 'default_encoding', 'UTF-8')
429 431 _bool_setting(settings, 'is_test', 'false')
430 432 _bool_setting(settings, 'gzip_responses', 'false')
431 433
432 434 # Call split out functions that sanitize settings for each topic.
433 435 _sanitize_appenlight_settings(settings)
434 436 _sanitize_vcs_settings(settings)
435 437
436 438 return settings
437 439
438 440
439 441 def _sanitize_appenlight_settings(settings):
440 442 _bool_setting(settings, 'appenlight', 'false')
441 443
442 444
443 445 def _sanitize_vcs_settings(settings):
444 446 """
445 447 Applies settings defaults and does type conversion for all VCS related
446 448 settings.
447 449 """
448 450 _string_setting(settings, 'vcs.svn.compatible_version', '')
449 451 _string_setting(settings, 'git_rev_filter', '--all')
450 452 _string_setting(settings, 'vcs.hooks.protocol', 'http')
451 453 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
452 454 _string_setting(settings, 'vcs.server', '')
453 455 _string_setting(settings, 'vcs.server.log_level', 'debug')
454 456 _string_setting(settings, 'vcs.server.protocol', 'http')
455 457 _bool_setting(settings, 'startup.import_repos', 'false')
456 458 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
457 459 _bool_setting(settings, 'vcs.server.enable', 'true')
458 460 _bool_setting(settings, 'vcs.start_server', 'false')
459 461 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
460 462 _int_setting(settings, 'vcs.connection_timeout', 3600)
461 463
462 464 # Support legacy values of vcs.scm_app_implementation. Legacy
463 465 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
464 466 # which is now mapped to 'http'.
465 467 scm_app_impl = settings['vcs.scm_app_implementation']
466 468 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
467 469 settings['vcs.scm_app_implementation'] = 'http'
468 470
469 471
470 472 def _int_setting(settings, name, default):
471 473 settings[name] = int(settings.get(name, default))
472 474
473 475
474 476 def _bool_setting(settings, name, default):
475 477 input = settings.get(name, default)
476 478 if isinstance(input, unicode):
477 479 input = input.encode('utf8')
478 480 settings[name] = asbool(input)
479 481
480 482
481 483 def _list_setting(settings, name, default):
482 484 raw_value = settings.get(name, default)
483 485
484 486 old_separator = ','
485 487 if old_separator in raw_value:
486 488 # If we get a comma separated list, pass it to our own function.
487 489 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
488 490 else:
489 491 # Otherwise we assume it uses pyramids space/newline separation.
490 492 settings[name] = aslist(raw_value)
491 493
492 494
493 495 def _string_setting(settings, name, default, lower=True):
494 496 value = settings.get(name, default)
495 497 if lower:
496 498 value = value.lower()
497 499 settings[name] = value
@@ -1,213 +1,248 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 import datetime
22 22 import logging
23 23 import pylons
24 24 import Queue
25 25 import subprocess32
26 import os
26 27
27 28 from pyramid.i18n import get_localizer
28 29 from pyramid.threadlocal import get_current_request
29 30 from threading import Thread
30 31
31 32 from rhodecode.translation import _ as tsf
32 33
33 34 import rhodecode
34 35
35 36 from pylons.i18n.translation import _get_translator
36 37 from pylons.util import ContextObj
37 38 from routes.util import URLGenerator
38 39
39 40 from rhodecode.lib.base import attach_context_attributes, get_auth_user
40 41
41 42 log = logging.getLogger(__name__)
42 43
43 44
44 45 def add_renderer_globals(event):
45 46 # Put pylons stuff into the context. This will be removed as soon as
46 47 # migration to pyramid is finished.
47 48 conf = pylons.config._current_obj()
48 49 event['h'] = conf.get('pylons.h')
49 50 event['c'] = pylons.tmpl_context
50 51 event['url'] = pylons.url
51 52
52 53 # TODO: When executed in pyramid view context the request is not available
53 54 # in the event. Find a better solution to get the request.
54 55 request = event['request'] or get_current_request()
55 56
56 57 # Add Pyramid translation as '_' to context
57 58 event['_'] = request.translate
58 59 event['_ungettext'] = request.plularize
59 60
60 61
61 62 def add_localizer(event):
62 63 request = event.request
63 64 localizer = get_localizer(request)
64 65
65 66 def auto_translate(*args, **kwargs):
66 67 return localizer.translate(tsf(*args, **kwargs))
67 68
68 69 request.localizer = localizer
69 70 request.translate = auto_translate
70 71 request.plularize = localizer.pluralize
71 72
72 73
73 74 def set_user_lang(event):
74 75 request = event.request
75 76 cur_user = getattr(request, 'user', None)
76 77
77 78 if cur_user:
78 79 user_lang = cur_user.get_instance().user_data.get('language')
79 80 if user_lang:
80 81 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
81 82 event.request._LOCALE_ = user_lang
82 83
83 84
84 85 def add_pylons_context(event):
85 86 request = event.request
86 87
87 88 config = rhodecode.CONFIG
88 89 environ = request.environ
89 90 session = request.session
90 91
91 92 if hasattr(request, 'vcs_call'):
92 93 # skip vcs calls
93 94 return
94 95
95 96 # Setup pylons globals.
96 97 pylons.config._push_object(config)
97 98 pylons.request._push_object(request)
98 99 pylons.session._push_object(session)
99 100 pylons.translator._push_object(_get_translator(config.get('lang')))
100 101
101 102 pylons.url._push_object(URLGenerator(config['routes.map'], environ))
102 103 session_key = (
103 104 config['pylons.environ_config'].get('session', 'beaker.session'))
104 105 environ[session_key] = session
105 106
106 107 if hasattr(request, 'rpc_method'):
107 108 # skip api calls
108 109 return
109 110
110 111 # Get the rhodecode auth user object and make it available.
111 112 auth_user = get_auth_user(environ)
112 113 request.user = auth_user
113 114 environ['rc_auth_user'] = auth_user
114 115
115 116 # Setup the pylons context object ('c')
116 117 context = ContextObj()
117 118 context.rhodecode_user = auth_user
118 119 attach_context_attributes(context, request)
119 120 pylons.tmpl_context._push_object(context)
120 121
121 122
122 123 def scan_repositories_if_enabled(event):
123 124 """
124 125 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
125 126 does a repository scan if enabled in the settings.
126 127 """
127 128 from rhodecode.model.scm import ScmModel
128 129 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
129 130 settings = event.app.registry.settings
130 131 vcs_server_enabled = settings['vcs.server.enable']
131 132 import_on_startup = settings['startup.import_repos']
132 133 if vcs_server_enabled and import_on_startup:
133 134 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
134 135 repo2db_mapper(repositories, remove_obsolete=False)
135 136
136 137
138 def write_metadata_if_needed(event):
139 """
140 Writes upgrade metadata
141 """
142 import rhodecode
143 from rhodecode.lib import system_info
144 from rhodecode.lib import ext_json
145
146 def write():
147 fname = '.rcmetadata.json'
148 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
149 metadata_destination = os.path.join(ini_loc, fname)
150
151 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
152 del dbinfo['url']
153 metadata = dict(
154 desc='upgrade metadata info',
155 created_on=datetime.datetime.utcnow().isoformat(),
156 usage=system_info.SysInfo(system_info.usage_info)()['value'],
157 platform=system_info.SysInfo(system_info.platform_type)()['value'],
158 database=dbinfo,
159 cpu=system_info.SysInfo(system_info.cpu)()['value'],
160 memory=system_info.SysInfo(system_info.memory)()['value'],
161 )
162
163 with open(metadata_destination, 'wb') as f:
164 f.write(ext_json.json.dumps(metadata))
165
166 try:
167 write()
168 except Exception:
169 pass
170
171
137 172 class Subscriber(object):
138 173 """
139 174 Base class for subscribers to the pyramid event system.
140 175 """
141 176 def __call__(self, event):
142 177 self.run(event)
143 178
144 179 def run(self, event):
145 180 raise NotImplementedError('Subclass has to implement this.')
146 181
147 182
148 183 class AsyncSubscriber(Subscriber):
149 184 """
150 185 Subscriber that handles the execution of events in a separate task to not
151 186 block the execution of the code which triggers the event. It puts the
152 187 received events into a queue from which the worker process takes them in
153 188 order.
154 189 """
155 190 def __init__(self):
156 191 self._stop = False
157 192 self._eventq = Queue.Queue()
158 193 self._worker = self.create_worker()
159 194 self._worker.start()
160 195
161 196 def __call__(self, event):
162 197 self._eventq.put(event)
163 198
164 199 def create_worker(self):
165 200 worker = Thread(target=self.do_work)
166 201 worker.daemon = True
167 202 return worker
168 203
169 204 def stop_worker(self):
170 205 self._stop = False
171 206 self._eventq.put(None)
172 207 self._worker.join()
173 208
174 209 def do_work(self):
175 210 while not self._stop:
176 211 event = self._eventq.get()
177 212 if event is not None:
178 213 self.run(event)
179 214
180 215
181 216 class AsyncSubprocessSubscriber(AsyncSubscriber):
182 217 """
183 218 Subscriber that uses the subprocess32 module to execute a command if an
184 219 event is received. Events are handled asynchronously.
185 220 """
186 221
187 222 def __init__(self, cmd, timeout=None):
188 223 super(AsyncSubprocessSubscriber, self).__init__()
189 224 self._cmd = cmd
190 225 self._timeout = timeout
191 226
192 227 def run(self, event):
193 228 cmd = self._cmd
194 229 timeout = self._timeout
195 230 log.debug('Executing command %s.', cmd)
196 231
197 232 try:
198 233 output = subprocess32.check_output(
199 234 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
200 235 log.debug('Command finished %s', cmd)
201 236 if output:
202 237 log.debug('Command output: %s', output)
203 238 except subprocess32.TimeoutExpired as e:
204 239 log.exception('Timeout while executing command.')
205 240 if e.output:
206 241 log.error('Command output: %s', e.output)
207 242 except subprocess32.CalledProcessError as e:
208 243 log.exception('Error while executing command.')
209 244 if e.output:
210 245 log.error('Command output: %s', e.output)
211 246 except:
212 247 log.exception(
213 248 'Exception while executing command %s.', cmd)
General Comments 0
You need to be logged in to leave comments. Login now