##// END OF EJS Templates
db: Move database initialization up to pyramid....
johbo -
r115:be1c5590 default
parent child Browse files
Show More
@@ -1,193 +1,189 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Pylons 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 # don't remove this import it does magic for celery
38 38 from rhodecode.lib import celerypylons # noqa
39 39
40 40 import rhodecode.lib.app_globals as app_globals
41 41
42 42 from rhodecode.config import utils
43 43 from rhodecode.config.routing import make_map
44 44 from rhodecode.config.jsroutes import generate_jsroutes_content
45 45
46 46 from rhodecode.lib import helpers
47 47 from rhodecode.lib.auth import set_available_permissions
48 48 from rhodecode.lib.utils import (
49 49 repo2db_mapper, make_db_config, set_rhodecode_config,
50 50 load_rcextensions)
51 51 from rhodecode.lib.utils2 import str2bool, aslist
52 52 from rhodecode.lib.vcs import connect_vcs, start_vcs_server
53 53 from rhodecode.model.scm import ScmModel
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57 def load_environment(global_conf, app_conf, initial=False,
58 58 test_env=None, test_index=None):
59 59 """
60 60 Configure the Pylons environment via the ``pylons.config``
61 61 object
62 62 """
63 63 config = PylonsConfig()
64 64
65 65 rhodecode.is_test = str2bool(app_conf.get('is_test', 'False'))
66 66
67 67 # Pylons paths
68 68 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
69 69 paths = {
70 70 'root': root,
71 71 'controllers': os.path.join(root, 'controllers'),
72 72 'static_files': os.path.join(root, 'public'),
73 73 'templates': [os.path.join(root, 'templates')],
74 74 }
75 75
76 76 # Initialize config with the basic options
77 77 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
78 78
79 79 # store some globals into rhodecode
80 80 rhodecode.CELERY_ENABLED = str2bool(config['app_conf'].get('use_celery'))
81 81 rhodecode.CELERY_EAGER = str2bool(
82 82 config['app_conf'].get('celery.always.eager'))
83 83
84 84 config['routes.map'] = make_map(config)
85 85
86 86 if asbool(config['debug']):
87 87 jsroutes = config['routes.map'].jsroutes()
88 88 jsroutes_file_content = generate_jsroutes_content(jsroutes)
89 89 jsroutes_file_path = os.path.join(
90 90 paths['static_files'], 'js', 'rhodecode', 'routes.js')
91 91
92 92 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
93 93 f.write(jsroutes_file_content)
94 94
95 95 config['pylons.app_globals'] = app_globals.Globals(config)
96 96 config['pylons.h'] = helpers
97 97 rhodecode.CONFIG = config
98 98
99 99 load_rcextensions(root_path=config['here'])
100 100
101 101 # Setup cache object as early as possible
102 102 import pylons
103 103 pylons.cache._push_object(config['pylons.app_globals'].cache)
104 104
105 105 # Create the Mako TemplateLookup, with the default auto-escaping
106 106 config['pylons.app_globals'].mako_lookup = TemplateLookup(
107 107 directories=paths['templates'],
108 108 error_handler=handle_mako_error,
109 109 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
110 110 input_encoding='utf-8', default_filters=['escape'],
111 111 imports=['from webhelpers.html import escape'])
112 112
113 113 # sets the c attribute access when don't existing attribute are accessed
114 114 config['pylons.strict_tmpl_context'] = True
115 115 config_file_name = os.path.split(config['__file__'])[-1]
116 116 test = re.match('^test[\w_]*\.ini$', config_file_name) is not None
117 117 if test:
118 118 if test_env is None:
119 119 test_env = not int(os.environ.get('RC_NO_TMP_PATH', 0))
120 120
121 121 from rhodecode.lib.utils import create_test_env, create_test_index
122 122 from rhodecode.tests import TESTS_TMP_PATH
123 123 # test repos
124 124 if test_env:
125 125 create_test_env(TESTS_TMP_PATH, config)
126 126 create_test_index(TESTS_TMP_PATH, config, True)
127 127
128 128 # Limit backends to "vcs.backends" from configuration
129 129 backends = config['vcs.backends'] = aslist(
130 130 config.get('vcs.backends', 'hg,git'), sep=',')
131 131 for alias in rhodecode.BACKENDS.keys():
132 132 if alias not in backends:
133 133 del rhodecode.BACKENDS[alias]
134 134 log.info("Enabled backends: %s", backends)
135 135
136 136 # initialize vcs client and optionally run the server if enabled
137 137 vcs_server_uri = config.get('vcs.server', '')
138 138 vcs_server_enabled = str2bool(config.get('vcs.server.enable', 'true'))
139 139 start_server = (
140 140 str2bool(config.get('vcs.start_server', 'false')) and
141 141 not int(os.environ.get('RC_VCSSERVER_TEST_DISABLE', '0')))
142 142 if vcs_server_enabled and start_server:
143 143 log.info("Starting vcsserver")
144 144 start_vcs_server(server_and_port=vcs_server_uri,
145 145 protocol=utils.get_vcs_server_protocol(config),
146 146 log_level=config['vcs.server.log_level'])
147 147
148 # MULTIPLE DB configs
149 # Setup the SQLAlchemy database engine
150 utils.initialize_database(config)
151
152 148 set_available_permissions(config)
153 149 db_cfg = make_db_config(clear_session=True)
154 150
155 151 repos_path = list(db_cfg.items('paths'))[0][1]
156 152 config['base_path'] = repos_path
157 153
158 154 config['vcs.hooks.direct_calls'] = _use_direct_hook_calls(config)
159 155 config['vcs.hooks.protocol'] = _get_vcs_hooks_protocol(config)
160 156
161 157 # store db config also in main global CONFIG
162 158 set_rhodecode_config(config)
163 159
164 160 # configure instance id
165 161 utils.set_instance_id(config)
166 162
167 163 # CONFIGURATION OPTIONS HERE (note: all config options will override
168 164 # any Pylons config options)
169 165
170 166 # store config reference into our module to skip import magic of pylons
171 167 rhodecode.CONFIG.update(config)
172 168
173 169 utils.configure_pyro4(config)
174 170 utils.configure_vcs(config)
175 171 if vcs_server_enabled:
176 172 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(config))
177 173
178 174 import_on_startup = str2bool(config.get('startup.import_repos', False))
179 175 if vcs_server_enabled and import_on_startup:
180 176 repo2db_mapper(ScmModel().repo_scan(repos_path), remove_obsolete=False)
181 177 return config
182 178
183 179
184 180 def _use_direct_hook_calls(config):
185 181 default_direct_hook_calls = 'false'
186 182 direct_hook_calls = str2bool(
187 183 config.get('vcs.hooks.direct_calls', default_direct_hook_calls))
188 184 return direct_hook_calls
189 185
190 186
191 187 def _get_vcs_hooks_protocol(config):
192 188 protocol = config.get('vcs.hooks.protocol', 'pyro4').lower()
193 189 return protocol
@@ -1,310 +1,315 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Pylons middleware initialization
23 23 """
24 24 import logging
25 25
26 26 from paste.registry import RegistryManager
27 27 from paste.gzipper import make_gzip_middleware
28 28 from pylons.middleware import ErrorHandler, StatusCodeRedirect
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.static import static_view
33 33 from pyramid.settings import asbool, aslist
34 34 from pyramid.wsgi import wsgiapp
35 35 from routes.middleware import RoutesMiddleware
36 36 import routes.util
37 37
38 38 import rhodecode
39 from rhodecode.config import patches
39 from rhodecode.config import patches, utils
40 40 from rhodecode.config.environment import load_environment
41 41 from rhodecode.lib.middleware import csrf
42 42 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
43 43 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
44 44 from rhodecode.lib.middleware.https_fixup import HttpsFixup
45 45 from rhodecode.lib.middleware.vcs import VCSMiddleware
46 46 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
53 53 """Create a Pylons WSGI application and return it
54 54
55 55 ``global_conf``
56 56 The inherited configuration for this application. Normally from
57 57 the [DEFAULT] section of the Paste ini file.
58 58
59 59 ``full_stack``
60 60 Whether or not this application provides a full WSGI stack (by
61 61 default, meaning it handles its own exceptions and errors).
62 62 Disable full_stack when this application is "managed" by
63 63 another WSGI middleware.
64 64
65 65 ``app_conf``
66 66 The application's local configuration. Normally specified in
67 67 the [app:<name>] section of the Paste ini file (where <name>
68 68 defaults to main).
69 69
70 70 """
71 71 # Apply compatibility patches
72 72 patches.kombu_1_5_1_python_2_7_11()
73 73 patches.inspect_getargspec()
74 74
75 75 # Configure the Pylons environment
76 76 config = load_environment(global_conf, app_conf)
77 77
78 78 # The Pylons WSGI app
79 79 app = PylonsApp(config=config)
80 80 if rhodecode.is_test:
81 81 app = csrf.CSRFDetector(app)
82 82
83 83 expected_origin = config.get('expected_origin')
84 84 if expected_origin:
85 85 # The API can be accessed from other Origins.
86 86 app = csrf.OriginChecker(app, expected_origin,
87 87 skip_urls=[routes.util.url_for('api')])
88 88
89 89 # Add RoutesMiddleware. Currently we have two instances in the stack. This
90 90 # is the lower one to make the StatusCodeRedirect middleware happy.
91 91 # TODO: johbo: This is not optimal, search for a better solution.
92 92 app = RoutesMiddleware(app, config['routes.map'])
93 93
94 94 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
95 95 if asbool(config['pdebug']):
96 96 from rhodecode.lib.profiler import ProfilingMiddleware
97 97 app = ProfilingMiddleware(app)
98 98
99 99 # Protect from VCS Server error related pages when server is not available
100 100 vcs_server_enabled = asbool(config.get('vcs.server.enable', 'true'))
101 101 if not vcs_server_enabled:
102 102 app = DisableVCSPagesWrapper(app)
103 103
104 104 if asbool(full_stack):
105 105
106 106 # Appenlight monitoring and error handler
107 107 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
108 108
109 109 # Handle Python exceptions
110 110 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
111 111
112 112 # we want our low level middleware to get to the request ASAP. We don't
113 113 # need any pylons stack middleware in them
114 114 app = VCSMiddleware(app, config, appenlight_client)
115 115 # Display error documents for 401, 403, 404 status codes (and
116 116 # 500 when debug is disabled)
117 117 if asbool(config['debug']):
118 118 app = StatusCodeRedirect(app)
119 119 else:
120 120 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
121 121
122 122 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
123 123 app = HttpsFixup(app, config)
124 124
125 125 # Establish the Registry for this application
126 126 app = RegistryManager(app)
127 127
128 128 app.config = config
129 129
130 130 return app
131 131
132 132
133 133 def make_pyramid_app(global_config, **settings):
134 134 """
135 135 Constructs the WSGI application based on Pyramid and wraps the Pylons based
136 136 application.
137 137
138 138 Specials:
139 139
140 140 * We migrate from Pylons to Pyramid. While doing this, we keep both
141 141 frameworks functional. This involves moving some WSGI middlewares around
142 142 and providing access to some data internals, so that the old code is
143 143 still functional.
144 144
145 145 * The application can also be integrated like a plugin via the call to
146 146 `includeme`. This is accompanied with the other utility functions which
147 147 are called. Changing this should be done with great care to not break
148 148 cases when these fragments are assembled from another place.
149 149
150 150 """
151 151 # The edition string should be available in pylons too, so we add it here
152 152 # before copying the settings.
153 153 settings.setdefault('rhodecode.edition', 'Community Edition')
154 154
155 155 # As long as our Pylons application does expect "unprepared" settings, make
156 156 # sure that we keep an unmodified copy. This avoids unintentional change of
157 157 # behavior in the old application.
158 158 settings_pylons = settings.copy()
159 159
160 # TODO: Remove this by refactoring the init DB function.
161 # Put debug flag into settings for DB setup.
162 settings['debug'] = global_config.get('debug', False)
163 utils.initialize_database(settings)
164
160 165 sanitize_settings_and_apply_defaults(settings)
161 166 config = Configurator(settings=settings)
162 167 add_pylons_compat_data(config.registry, global_config, settings_pylons)
163 168 includeme(config)
164 169 includeme_last(config)
165 170 pyramid_app = config.make_wsgi_app()
166 171 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
167 172 return pyramid_app
168 173
169 174
170 175 def add_pylons_compat_data(registry, global_config, settings):
171 176 """
172 177 Attach data to the registry to support the Pylons integration.
173 178 """
174 179 registry._pylons_compat_global_config = global_config
175 180 registry._pylons_compat_settings = settings
176 181
177 182
178 183 def includeme(config):
179 184 settings = config.registry.settings
180 185
181 186 # Includes which are required. The application would fail without them.
182 187 config.include('pyramid_mako')
183 188 config.include('pyramid_beaker')
184 189 config.include('rhodecode.authentication')
185 190 config.include('rhodecode.login')
186 191 config.include('rhodecode.tweens')
187 192 config.include('rhodecode.api')
188 193
189 194 # Set the authorization policy.
190 195 authz_policy = ACLAuthorizationPolicy()
191 196 config.set_authorization_policy(authz_policy)
192 197
193 198 # Set the default renderer for HTML templates to mako.
194 199 config.add_mako_renderer('.html')
195 200
196 201 # plugin information
197 202 config.registry.rhodecode_plugins = {}
198 203
199 204 config.add_directive(
200 205 'register_rhodecode_plugin', register_rhodecode_plugin)
201 206 # include RhodeCode plugins
202 207 includes = aslist(settings.get('rhodecode.includes', []))
203 208 for inc in includes:
204 209 config.include(inc)
205 210
206 211 # This is the glue which allows us to migrate in chunks. By registering the
207 212 # pylons based application as the "Not Found" view in Pyramid, we will
208 213 # fallback to the old application each time the new one does not yet know
209 214 # how to handle a request.
210 215 pylons_app = make_app(
211 216 config.registry._pylons_compat_global_config,
212 217 **config.registry._pylons_compat_settings)
213 218 config.registry._pylons_compat_config = pylons_app.config
214 219 pylons_app_as_view = wsgiapp(pylons_app)
215 220 config.add_notfound_view(pylons_app_as_view)
216 221
217 222
218 223 def includeme_last(config):
219 224 """
220 225 The static file catchall needs to be last in the view configuration.
221 226 """
222 227 settings = config.registry.settings
223 228
224 229 # Note: johbo: I would prefer to register a prefix for static files at some
225 230 # point, e.g. move them under '_static/'. This would fully avoid that we
226 231 # can have name clashes with a repository name. Imaging someone calling his
227 232 # repo "css" ;-) Also having an external web server to serve out the static
228 233 # files seems to be easier to set up if they have a common prefix.
229 234 #
230 235 # Example: config.add_static_view('_static', path='rhodecode:public')
231 236 #
232 237 # It might be an option to register both paths for a while and then migrate
233 238 # over to the new location.
234 239
235 240 # Serving static files with a catchall.
236 241 if settings['static_files']:
237 242 config.add_route('catchall_static', '/*subpath')
238 243 config.add_view(
239 244 static_view('rhodecode:public'), route_name='catchall_static')
240 245
241 246
242 247 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
243 248 """
244 249 Apply outer WSGI middlewares around the application.
245 250
246 251 Part of this has been moved up from the Pylons layer, so that the
247 252 data is also available if old Pylons code is hit through an already ported
248 253 view.
249 254 """
250 255 settings = config.registry.settings
251 256
252 257 # Add RoutesMiddleware. Currently we have two instances in the stack. This
253 258 # is the upper one to support the pylons compatibility tween during
254 259 # migration to pyramid.
255 260 pyramid_app = RoutesMiddleware(
256 261 pyramid_app, config.registry._pylons_compat_config['routes.map'])
257 262
258 263 # TODO: johbo: Don't really see why we enable the gzip middleware when
259 264 # serving static files, might be something that should have its own setting
260 265 # as well?
261 266 if settings['static_files']:
262 267 pyramid_app = make_gzip_middleware(
263 268 pyramid_app, settings, compress_level=1)
264 269
265 270 return pyramid_app
266 271
267 272
268 273 def sanitize_settings_and_apply_defaults(settings):
269 274 """
270 275 Applies settings defaults and does all type conversion.
271 276
272 277 We would move all settings parsing and preparation into this place, so that
273 278 we have only one place left which deals with this part. The remaining parts
274 279 of the application would start to rely fully on well prepared settings.
275 280
276 281 This piece would later be split up per topic to avoid a big fat monster
277 282 function.
278 283 """
279 284
280 285 # Pyramid's mako renderer has to search in the templates folder so that the
281 286 # old templates still work. Ported and new templates are expected to use
282 287 # real asset specifications for the includes.
283 288 mako_directories = settings.setdefault('mako.directories', [
284 289 # Base templates of the original Pylons application
285 290 'rhodecode:templates',
286 291 ])
287 292 log.debug(
288 293 "Using the following Mako template directories: %s",
289 294 mako_directories)
290 295
291 296 # Default includes, possible to change as a user
292 297 pyramid_includes = settings.setdefault('pyramid.includes', [
293 298 'rhodecode.lib.middleware.request_wrapper',
294 299 ])
295 300 log.debug(
296 301 "Using the following pyramid.includes: %s",
297 302 pyramid_includes)
298 303
299 304 # TODO: johbo: Re-think this, usually the call to config.include
300 305 # should allow to pass in a prefix.
301 306 settings.setdefault('rhodecode.api.url', '/_admin/api')
302 307
303 308 _bool_setting(settings, 'vcs.server.enable', 'true')
304 309 _bool_setting(settings, 'static_files', 'true')
305 310
306 311 return settings
307 312
308 313
309 314 def _bool_setting(settings, name, default):
310 315 settings[name] = asbool(settings.get(name, default))
General Comments 0
You need to be logged in to leave comments. Login now