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