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