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