##// END OF EJS Templates
debug: add new custom logging to track unique requests across systems.
marcink -
r2794:9b8d6c69 default
parent child Browse files
Show More
@@ -0,0 +1,29 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-2018 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from uuid import uuid4
22 from pyramid.decorator import reify
23 from pyramid.request import Request as _Request
24
25
26 class Request(_Request):
27 @reify
28 def req_id(self):
29 return str(uuid4())
@@ -1,438 +1,440 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import traceback
23 23 import collections
24 24
25 25 from paste.gzipper import make_gzip_middleware
26 26 from pyramid.wsgi import wsgiapp
27 27 from pyramid.authorization import ACLAuthorizationPolicy
28 28 from pyramid.config import Configurator
29 29 from pyramid.settings import asbool, aslist
30 30 from pyramid.httpexceptions import (
31 31 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
32 32 from pyramid.events import ApplicationCreated
33 33 from pyramid.renderers import render_to_response
34 34
35 35 from rhodecode.model import meta
36 36 from rhodecode.config import patches
37 37 from rhodecode.config import utils as config_utils
38 38 from rhodecode.config.environment import load_pyramid_environment
39 39
40 40 from rhodecode.lib.middleware.vcs import VCSMiddleware
41 from rhodecode.lib.request import Request
41 42 from rhodecode.lib.vcs import VCSCommunicationError
42 43 from rhodecode.lib.exceptions import VCSServerUnavailable
43 44 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
44 45 from rhodecode.lib.middleware.https_fixup import HttpsFixup
45 46 from rhodecode.lib.celerylib.loader import configure_celery
46 47 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
47 48 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
48 49 from rhodecode.subscribers import (
49 50 scan_repositories_if_enabled, write_js_routes_if_enabled,
50 51 write_metadata_if_needed, inject_app_settings)
51 52
52 53
53 54 log = logging.getLogger(__name__)
54 55
55 56
56 57 def is_http_error(response):
57 58 # error which should have traceback
58 59 return response.status_code > 499
59 60
60 61
61 62 def make_pyramid_app(global_config, **settings):
62 63 """
63 64 Constructs the WSGI application based on Pyramid.
64 65
65 66 Specials:
66 67
67 68 * The application can also be integrated like a plugin via the call to
68 69 `includeme`. This is accompanied with the other utility functions which
69 70 are called. Changing this should be done with great care to not break
70 71 cases when these fragments are assembled from another place.
71 72
72 73 """
73 74 sanitize_settings_and_apply_defaults(settings)
74 75
75 76 config = Configurator(settings=settings)
76 77
77 78 # Apply compatibility patches
78 79 patches.inspect_getargspec()
79 80
80 81 load_pyramid_environment(global_config, settings)
81 82
82 83 # Static file view comes first
83 84 includeme_first(config)
84 85
85 86 includeme(config)
86 87
87 88 pyramid_app = config.make_wsgi_app()
88 89 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
89 90 pyramid_app.config = config
90 91
91 92 config.configure_celery(global_config['__file__'])
92 93 # creating the app uses a connection - return it after we are done
93 94 meta.Session.remove()
94 95
95 96 log.info('Pyramid app %s created and configured.', pyramid_app)
96 97 return pyramid_app
97 98
98 99
99 100 def not_found_view(request):
100 101 """
101 102 This creates the view which should be registered as not-found-view to
102 103 pyramid.
103 104 """
104 105
105 106 if not getattr(request, 'vcs_call', None):
106 107 # handle like regular case with our error_handler
107 108 return error_handler(HTTPNotFound(), request)
108 109
109 110 # handle not found view as a vcs call
110 111 settings = request.registry.settings
111 112 ae_client = getattr(request, 'ae_client', None)
112 113 vcs_app = VCSMiddleware(
113 114 HTTPNotFound(), request.registry, settings,
114 115 appenlight_client=ae_client)
115 116
116 117 return wsgiapp(vcs_app)(None, request)
117 118
118 119
119 120 def error_handler(exception, request):
120 121 import rhodecode
121 122 from rhodecode.lib import helpers
122 123
123 124 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
124 125
125 126 base_response = HTTPInternalServerError()
126 127 # prefer original exception for the response since it may have headers set
127 128 if isinstance(exception, HTTPException):
128 129 base_response = exception
129 130 elif isinstance(exception, VCSCommunicationError):
130 131 base_response = VCSServerUnavailable()
131 132
132 133 if is_http_error(base_response):
133 134 log.exception(
134 135 'error occurred handling this request for path: %s', request.path)
135 136
136 137 error_explanation = base_response.explanation or str(base_response)
137 138 if base_response.status_code == 404:
138 139 error_explanation += " Or you don't have permission to access it."
139 140 c = AttributeDict()
140 141 c.error_message = base_response.status
141 142 c.error_explanation = error_explanation
142 143 c.visual = AttributeDict()
143 144
144 145 c.visual.rhodecode_support_url = (
145 146 request.registry.settings.get('rhodecode_support_url') or
146 147 request.route_url('rhodecode_support')
147 148 )
148 149 c.redirect_time = 0
149 150 c.rhodecode_name = rhodecode_title
150 151 if not c.rhodecode_name:
151 152 c.rhodecode_name = 'Rhodecode'
152 153
153 154 c.causes = []
154 155 if is_http_error(base_response):
155 156 c.causes.append('Server is overloaded.')
156 157 c.causes.append('Server database connection is lost.')
157 158 c.causes.append('Server expected unhandled error.')
158 159
159 160 if hasattr(base_response, 'causes'):
160 161 c.causes = base_response.causes
161 162
162 163 c.messages = helpers.flash.pop_messages(request=request)
163 164 c.traceback = traceback.format_exc()
164 165 response = render_to_response(
165 166 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
166 167 response=base_response)
167 168
168 169 return response
169 170
170 171
171 172 def includeme_first(config):
172 173 # redirect automatic browser favicon.ico requests to correct place
173 174 def favicon_redirect(context, request):
174 175 return HTTPFound(
175 176 request.static_path('rhodecode:public/images/favicon.ico'))
176 177
177 178 config.add_view(favicon_redirect, route_name='favicon')
178 179 config.add_route('favicon', '/favicon.ico')
179 180
180 181 def robots_redirect(context, request):
181 182 return HTTPFound(
182 183 request.static_path('rhodecode:public/robots.txt'))
183 184
184 185 config.add_view(robots_redirect, route_name='robots')
185 186 config.add_route('robots', '/robots.txt')
186 187
187 188 config.add_static_view(
188 189 '_static/deform', 'deform:static')
189 190 config.add_static_view(
190 191 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
191 192
192 193
193 194 def includeme(config):
194 195 settings = config.registry.settings
196 config.set_request_factory(Request)
195 197
196 198 # plugin information
197 199 config.registry.rhodecode_plugins = collections.OrderedDict()
198 200
199 201 config.add_directive(
200 202 'register_rhodecode_plugin', register_rhodecode_plugin)
201 203
202 204 config.add_directive('configure_celery', configure_celery)
203 205
204 206 if asbool(settings.get('appenlight', 'false')):
205 207 config.include('appenlight_client.ext.pyramid_tween')
206 208
207 209 # Includes which are required. The application would fail without them.
208 210 config.include('pyramid_mako')
209 211 config.include('pyramid_beaker')
210 212 config.include('rhodecode.lib.caches')
211 213
212 214 config.include('rhodecode.authentication')
213 215 config.include('rhodecode.integrations')
214 216
215 217 # apps
216 218 config.include('rhodecode.apps._base')
217 219 config.include('rhodecode.apps.ops')
218 220
219 221 config.include('rhodecode.apps.admin')
220 222 config.include('rhodecode.apps.channelstream')
221 223 config.include('rhodecode.apps.login')
222 224 config.include('rhodecode.apps.home')
223 225 config.include('rhodecode.apps.journal')
224 226 config.include('rhodecode.apps.repository')
225 227 config.include('rhodecode.apps.repo_group')
226 228 config.include('rhodecode.apps.user_group')
227 229 config.include('rhodecode.apps.search')
228 230 config.include('rhodecode.apps.user_profile')
229 231 config.include('rhodecode.apps.user_group_profile')
230 232 config.include('rhodecode.apps.my_account')
231 233 config.include('rhodecode.apps.svn_support')
232 234 config.include('rhodecode.apps.ssh_support')
233 235 config.include('rhodecode.apps.gist')
234 236
235 237 config.include('rhodecode.apps.debug_style')
236 238 config.include('rhodecode.tweens')
237 239 config.include('rhodecode.api')
238 240
239 241 config.add_route(
240 242 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
241 243
242 244 config.add_translation_dirs('rhodecode:i18n/')
243 245 settings['default_locale_name'] = settings.get('lang', 'en')
244 246
245 247 # Add subscribers.
246 248 config.add_subscriber(inject_app_settings, ApplicationCreated)
247 249 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
248 250 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
249 251 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
250 252
251 253 # events
252 254 # TODO(marcink): this should be done when pyramid migration is finished
253 255 # config.add_subscriber(
254 256 # 'rhodecode.integrations.integrations_event_handler',
255 257 # 'rhodecode.events.RhodecodeEvent')
256 258
257 259 # request custom methods
258 260 config.add_request_method(
259 261 'rhodecode.lib.partial_renderer.get_partial_renderer',
260 262 'get_partial_renderer')
261 263
262 264 # Set the authorization policy.
263 265 authz_policy = ACLAuthorizationPolicy()
264 266 config.set_authorization_policy(authz_policy)
265 267
266 268 # Set the default renderer for HTML templates to mako.
267 269 config.add_mako_renderer('.html')
268 270
269 271 config.add_renderer(
270 272 name='json_ext',
271 273 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
272 274
273 275 # include RhodeCode plugins
274 276 includes = aslist(settings.get('rhodecode.includes', []))
275 277 for inc in includes:
276 278 config.include(inc)
277 279
278 280 # custom not found view, if our pyramid app doesn't know how to handle
279 281 # the request pass it to potential VCS handling ap
280 282 config.add_notfound_view(not_found_view)
281 283 if not settings.get('debugtoolbar.enabled', False):
282 284 # disabled debugtoolbar handle all exceptions via the error_handlers
283 285 config.add_view(error_handler, context=Exception)
284 286
285 287 # all errors including 403/404/50X
286 288 config.add_view(error_handler, context=HTTPError)
287 289
288 290
289 291 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
290 292 """
291 293 Apply outer WSGI middlewares around the application.
292 294 """
293 295 settings = config.registry.settings
294 296
295 297 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
296 298 pyramid_app = HttpsFixup(pyramid_app, settings)
297 299
298 300 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
299 301 pyramid_app, settings)
300 302 config.registry.ae_client = _ae_client
301 303
302 304 if settings['gzip_responses']:
303 305 pyramid_app = make_gzip_middleware(
304 306 pyramid_app, settings, compress_level=1)
305 307
306 308 # this should be the outer most middleware in the wsgi stack since
307 309 # middleware like Routes make database calls
308 310 def pyramid_app_with_cleanup(environ, start_response):
309 311 try:
310 312 return pyramid_app(environ, start_response)
311 313 finally:
312 314 # Dispose current database session and rollback uncommitted
313 315 # transactions.
314 316 meta.Session.remove()
315 317
316 318 # In a single threaded mode server, on non sqlite db we should have
317 319 # '0 Current Checked out connections' at the end of a request,
318 320 # if not, then something, somewhere is leaving a connection open
319 321 pool = meta.Base.metadata.bind.engine.pool
320 322 log.debug('sa pool status: %s', pool.status())
321 323
322 324 return pyramid_app_with_cleanup
323 325
324 326
325 327 def sanitize_settings_and_apply_defaults(settings):
326 328 """
327 329 Applies settings defaults and does all type conversion.
328 330
329 331 We would move all settings parsing and preparation into this place, so that
330 332 we have only one place left which deals with this part. The remaining parts
331 333 of the application would start to rely fully on well prepared settings.
332 334
333 335 This piece would later be split up per topic to avoid a big fat monster
334 336 function.
335 337 """
336 338
337 339 settings.setdefault('rhodecode.edition', 'Community Edition')
338 340
339 341 if 'mako.default_filters' not in settings:
340 342 # set custom default filters if we don't have it defined
341 343 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
342 344 settings['mako.default_filters'] = 'h_filter'
343 345
344 346 if 'mako.directories' not in settings:
345 347 mako_directories = settings.setdefault('mako.directories', [
346 348 # Base templates of the original application
347 349 'rhodecode:templates',
348 350 ])
349 351 log.debug(
350 352 "Using the following Mako template directories: %s",
351 353 mako_directories)
352 354
353 355 # Default includes, possible to change as a user
354 356 pyramid_includes = settings.setdefault('pyramid.includes', [
355 357 'rhodecode.lib.middleware.request_wrapper',
356 358 ])
357 359 log.debug(
358 360 "Using the following pyramid.includes: %s",
359 361 pyramid_includes)
360 362
361 363 # TODO: johbo: Re-think this, usually the call to config.include
362 364 # should allow to pass in a prefix.
363 365 settings.setdefault('rhodecode.api.url', '/_admin/api')
364 366
365 367 # Sanitize generic settings.
366 368 _list_setting(settings, 'default_encoding', 'UTF-8')
367 369 _bool_setting(settings, 'is_test', 'false')
368 370 _bool_setting(settings, 'gzip_responses', 'false')
369 371
370 372 # Call split out functions that sanitize settings for each topic.
371 373 _sanitize_appenlight_settings(settings)
372 374 _sanitize_vcs_settings(settings)
373 375
374 376 # configure instance id
375 377 config_utils.set_instance_id(settings)
376 378
377 379 return settings
378 380
379 381
380 382 def _sanitize_appenlight_settings(settings):
381 383 _bool_setting(settings, 'appenlight', 'false')
382 384
383 385
384 386 def _sanitize_vcs_settings(settings):
385 387 """
386 388 Applies settings defaults and does type conversion for all VCS related
387 389 settings.
388 390 """
389 391 _string_setting(settings, 'vcs.svn.compatible_version', '')
390 392 _string_setting(settings, 'git_rev_filter', '--all')
391 393 _string_setting(settings, 'vcs.hooks.protocol', 'http')
392 394 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
393 395 _string_setting(settings, 'vcs.server', '')
394 396 _string_setting(settings, 'vcs.server.log_level', 'debug')
395 397 _string_setting(settings, 'vcs.server.protocol', 'http')
396 398 _bool_setting(settings, 'startup.import_repos', 'false')
397 399 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
398 400 _bool_setting(settings, 'vcs.server.enable', 'true')
399 401 _bool_setting(settings, 'vcs.start_server', 'false')
400 402 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
401 403 _int_setting(settings, 'vcs.connection_timeout', 3600)
402 404
403 405 # Support legacy values of vcs.scm_app_implementation. Legacy
404 406 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
405 407 # which is now mapped to 'http'.
406 408 scm_app_impl = settings['vcs.scm_app_implementation']
407 409 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
408 410 settings['vcs.scm_app_implementation'] = 'http'
409 411
410 412
411 413 def _int_setting(settings, name, default):
412 414 settings[name] = int(settings.get(name, default))
413 415
414 416
415 417 def _bool_setting(settings, name, default):
416 418 input_val = settings.get(name, default)
417 419 if isinstance(input_val, unicode):
418 420 input_val = input_val.encode('utf8')
419 421 settings[name] = asbool(input_val)
420 422
421 423
422 424 def _list_setting(settings, name, default):
423 425 raw_value = settings.get(name, default)
424 426
425 427 old_separator = ','
426 428 if old_separator in raw_value:
427 429 # If we get a comma separated list, pass it to our own function.
428 430 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
429 431 else:
430 432 # Otherwise we assume it uses pyramids space/newline separation.
431 433 settings[name] = aslist(raw_value)
432 434
433 435
434 436 def _string_setting(settings, name, default, lower=True):
435 437 value = settings.get(name, default)
436 438 if lower:
437 439 value = value.lower()
438 440 settings[name] = value
@@ -1,141 +1,170 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import sys
22 22 import logging
23 23
24 24
25 25 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
26 26
27 27 # Sequences
28 28 RESET_SEQ = "\033[0m"
29 29 COLOR_SEQ = "\033[0;%dm"
30 30 BOLD_SEQ = "\033[1m"
31 31
32 32 COLORS = {
33 33 'CRITICAL': MAGENTA,
34 34 'ERROR': RED,
35 35 'WARNING': CYAN,
36 36 'INFO': GREEN,
37 37 'DEBUG': BLUE,
38 38 'SQL': YELLOW
39 39 }
40 40
41 41
42 42 def one_space_trim(s):
43 43 if s.find(" ") == -1:
44 44 return s
45 45 else:
46 46 s = s.replace(' ', ' ')
47 47 return one_space_trim(s)
48 48
49 49
50 50 def format_sql(sql):
51 51 sql = sql.replace('\n', '')
52 52 sql = one_space_trim(sql)
53 53 sql = sql\
54 54 .replace(',', ',\n\t')\
55 55 .replace('SELECT', '\n\tSELECT \n\t')\
56 56 .replace('UPDATE', '\n\tUPDATE \n\t')\
57 57 .replace('DELETE', '\n\tDELETE \n\t')\
58 58 .replace('FROM', '\n\tFROM')\
59 59 .replace('ORDER BY', '\n\tORDER BY')\
60 60 .replace('LIMIT', '\n\tLIMIT')\
61 61 .replace('WHERE', '\n\tWHERE')\
62 62 .replace('AND', '\n\tAND')\
63 63 .replace('LEFT', '\n\tLEFT')\
64 64 .replace('INNER', '\n\tINNER')\
65 65 .replace('INSERT', '\n\tINSERT')\
66 66 .replace('DELETE', '\n\tDELETE')
67 67 return sql
68 68
69 69
70 70 class ExceptionAwareFormatter(logging.Formatter):
71 71 """
72 72 Extended logging formatter which prints out remote tracebacks.
73 73 """
74 74
75 75 def formatException(self, ei):
76 76 ex_type, ex_value, ex_tb = ei
77 77
78 78 local_tb = logging.Formatter.formatException(self, ei)
79 79 if hasattr(ex_value, '_vcs_server_traceback'):
80 80
81 81 def formatRemoteTraceback(remote_tb_lines):
82 82 result = ["\n +--- This exception occured remotely on VCSServer - Remote traceback:\n\n"]
83 83 result.append(remote_tb_lines)
84 84 result.append("\n +--- End of remote traceback\n")
85 85 return result
86 86
87 87 try:
88 88 if ex_type is not None and ex_value is None and ex_tb is None:
89 89 # possible old (3.x) call syntax where caller is only
90 90 # providing exception object
91 91 if type(ex_type) is not type:
92 92 raise TypeError(
93 93 "invalid argument: ex_type should be an exception "
94 94 "type, or just supply no arguments at all")
95 95 if ex_type is None and ex_tb is None:
96 96 ex_type, ex_value, ex_tb = sys.exc_info()
97 97
98 98 remote_tb = getattr(ex_value, "_vcs_server_traceback", None)
99 99
100 100 if remote_tb:
101 101 remote_tb = formatRemoteTraceback(remote_tb)
102 102 return local_tb + ''.join(remote_tb)
103 103 finally:
104 104 # clean up cycle to traceback, to allow proper GC
105 105 del ex_type, ex_value, ex_tb
106 106
107 107 return local_tb
108 108
109 109
110 110 class ColorFormatter(ExceptionAwareFormatter):
111 111
112 112 def format(self, record):
113 113 """
114 114 Changes record's levelname to use with COLORS enum
115 115 """
116 116
117 117 levelname = record.levelname
118 118 start = COLOR_SEQ % (COLORS[levelname])
119 119 def_record = logging.Formatter.format(self, record)
120 120 end = RESET_SEQ
121 121
122 122 colored_record = ''.join([start, def_record, end])
123 123 return colored_record
124 124
125 125
126 def _inject_req_id(record):
127 from pyramid.threadlocal import get_current_request
128 req = get_current_request()
129 req_id = 'req_id:%-36s ' % (getattr(req, 'req_id', None))
130 record.req_id = req_id
131
132
133 class RequestTrackingFormatter(ExceptionAwareFormatter):
134 def format(self, record):
135 _inject_req_id(record)
136 def_record = logging.Formatter.format(self, record)
137 return def_record
138
139
140 class ColorRequestTrackingFormatter(ColorFormatter):
141 def format(self, record):
142 """
143 Changes record's levelname to use with COLORS enum
144 """
145 _inject_req_id(record)
146 levelname = record.levelname
147 start = COLOR_SEQ % (COLORS[levelname])
148 def_record = logging.Formatter.format(self, record)
149 end = RESET_SEQ
150
151 colored_record = ''.join([start, def_record, end])
152 return colored_record
153
154
126 155 class ColorFormatterSql(logging.Formatter):
127 156
128 157 def format(self, record):
129 158 """
130 159 Changes record's levelname to use with COLORS enum
131 160 """
132 161
133 162 start = COLOR_SEQ % (COLORS['SQL'])
134 163 def_record = format_sql(logging.Formatter.format(self, record))
135 164 end = RESET_SEQ
136 165
137 166 colored_record = ''.join([start, def_record, end])
138 167 return colored_record
139 168
140 169 # marcink: needs to stay with this name for backward .ini compatability
141 170 Pyro4AwareFormatter = ExceptionAwareFormatter
@@ -1,244 +1,244 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import gzip
22 22 import shutil
23 23 import logging
24 24 import tempfile
25 25 import urlparse
26 26
27 27 from webob.exc import HTTPNotFound
28 28
29 29 import rhodecode
30 30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
31 31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
32 32 from rhodecode.lib.middleware.simplehg import SimpleHg
33 33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
34 34 from rhodecode.model.settings import VcsSettingsModel
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38 VCS_TYPE_KEY = '_rc_vcs_type'
39 39 VCS_TYPE_SKIP = '_rc_vcs_skip'
40 40
41 41
42 42 def is_git(environ):
43 43 """
44 44 Returns True if requests should be handled by GIT wsgi middleware
45 45 """
46 46 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
47 47 log.debug(
48 48 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
49 49 is_git_path is not None)
50 50
51 51 return is_git_path
52 52
53 53
54 54 def is_hg(environ):
55 55 """
56 56 Returns True if requests target is mercurial server - header
57 57 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
58 58 """
59 59 is_hg_path = False
60 60
61 61 http_accept = environ.get('HTTP_ACCEPT')
62 62
63 63 if http_accept and http_accept.startswith('application/mercurial'):
64 64 query = urlparse.parse_qs(environ['QUERY_STRING'])
65 65 if 'cmd' in query:
66 66 is_hg_path = True
67 67
68 68 log.debug(
69 69 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
70 70 is_hg_path)
71 71
72 72 return is_hg_path
73 73
74 74
75 75 def is_svn(environ):
76 76 """
77 77 Returns True if requests target is Subversion server
78 78 """
79 79
80 80 http_dav = environ.get('HTTP_DAV', '')
81 81 magic_path_segment = rhodecode.CONFIG.get(
82 82 'rhodecode_subversion_magic_path', '/!svn')
83 83 is_svn_path = (
84 84 'subversion' in http_dav or
85 85 magic_path_segment in environ['PATH_INFO']
86 86 or environ['REQUEST_METHOD'] in ['PROPFIND', 'PROPPATCH']
87 87 )
88 88 log.debug(
89 89 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
90 90 is_svn_path)
91 91
92 92 return is_svn_path
93 93
94 94
95 95 class GunzipMiddleware(object):
96 96 """
97 97 WSGI middleware that unzips gzip-encoded requests before
98 98 passing on to the underlying application.
99 99 """
100 100
101 101 def __init__(self, application):
102 102 self.app = application
103 103
104 104 def __call__(self, environ, start_response):
105 105 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
106 106
107 107 if b'gzip' in accepts_encoding_header:
108 108 log.debug('gzip detected, now running gunzip wrapper')
109 109 wsgi_input = environ['wsgi.input']
110 110
111 111 if not hasattr(environ['wsgi.input'], 'seek'):
112 112 # The gzip implementation in the standard library of Python 2.x
113 113 # requires the '.seek()' and '.tell()' methods to be available
114 114 # on the input stream. Read the data into a temporary file to
115 115 # work around this limitation.
116 116
117 117 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
118 118 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
119 119 wsgi_input.seek(0)
120 120
121 121 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
122 122 # since we "Ungzipped" the content we say now it's no longer gzip
123 123 # content encoding
124 124 del environ['HTTP_CONTENT_ENCODING']
125 125
126 126 # content length has changes ? or i'm not sure
127 127 if 'CONTENT_LENGTH' in environ:
128 128 del environ['CONTENT_LENGTH']
129 129 else:
130 130 log.debug('content not gzipped, gzipMiddleware passing '
131 131 'request further')
132 132 return self.app(environ, start_response)
133 133
134 134
135 135 def is_vcs_call(environ):
136 136 if VCS_TYPE_KEY in environ:
137 137 raw_type = environ[VCS_TYPE_KEY]
138 138 return raw_type and raw_type != VCS_TYPE_SKIP
139 139 return False
140 140
141 141
142 142 def detect_vcs_request(environ, backends):
143 143 checks = {
144 144 'hg': (is_hg, SimpleHg),
145 145 'git': (is_git, SimpleGit),
146 146 'svn': (is_svn, SimpleSvn),
147 147 }
148 148 handler = None
149 149
150 150 if VCS_TYPE_KEY in environ:
151 151 raw_type = environ[VCS_TYPE_KEY]
152 152 if raw_type == VCS_TYPE_SKIP:
153 153 log.debug('got `skip` marker for vcs detection, skipping...')
154 154 return handler
155 155
156 156 _check, handler = checks.get(raw_type) or [None, None]
157 157 if handler:
158 158 log.debug('got handler:%s from environ', handler)
159 159
160 160 if not handler:
161 log.debug('checking if request is of VCS type in order: %s', backends)
161 log.debug('request start: checking if request is of VCS type in order: %s', backends)
162 162 for vcs_type in backends:
163 163 vcs_check, _handler = checks[vcs_type]
164 164 if vcs_check(environ):
165 165 log.debug('vcs handler found %s', _handler)
166 166 handler = _handler
167 167 break
168 168
169 169 return handler
170 170
171 171
172 172 class VCSMiddleware(object):
173 173
174 174 def __init__(self, app, registry, config, appenlight_client):
175 175 self.application = app
176 176 self.registry = registry
177 177 self.config = config
178 178 self.appenlight_client = appenlight_client
179 179 self.use_gzip = True
180 180 # order in which we check the middlewares, based on vcs.backends config
181 181 self.check_middlewares = config['vcs.backends']
182 182
183 183 def vcs_config(self, repo_name=None):
184 184 """
185 185 returns serialized VcsSettings
186 186 """
187 187 try:
188 188 return VcsSettingsModel(
189 189 repo=repo_name).get_ui_settings_as_config_obj()
190 190 except Exception:
191 191 pass
192 192
193 193 def wrap_in_gzip_if_enabled(self, app, config):
194 194 if self.use_gzip:
195 195 app = GunzipMiddleware(app)
196 196 return app
197 197
198 198 def _get_handler_app(self, environ):
199 199 app = None
200 200 log.debug('VCSMiddleware: detecting vcs type.')
201 201 handler = detect_vcs_request(environ, self.check_middlewares)
202 202 if handler:
203 203 app = handler(self.config, self.registry)
204 204
205 205 return app
206 206
207 207 def __call__(self, environ, start_response):
208 208 # check if we handle one of interesting protocols, optionally extract
209 209 # specific vcsSettings and allow changes of how things are wrapped
210 210 vcs_handler = self._get_handler_app(environ)
211 211 if vcs_handler:
212 212 # translate the _REPO_ID into real repo NAME for usage
213 213 # in middleware
214 214 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
215 215
216 216 # Set acl, url and vcs repo names.
217 217 vcs_handler.set_repo_names(environ)
218 218
219 219 # register repo config back to the handler
220 220 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
221 221 # maybe damaged/non existent settings. We still want to
222 222 # pass that point to validate on is_valid_and_existing_repo
223 223 # and return proper HTTP Code back to client
224 224 if vcs_conf:
225 225 vcs_handler.repo_vcs_config = vcs_conf
226 226
227 227 # check for type, presence in database and on filesystem
228 228 if not vcs_handler.is_valid_and_existing_repo(
229 229 vcs_handler.acl_repo_name,
230 230 vcs_handler.base_path,
231 231 vcs_handler.SCM):
232 232 return HTTPNotFound()(environ, start_response)
233 233
234 234 environ['REPO_NAME'] = vcs_handler.url_repo_name
235 235
236 236 # Wrap handler in middlewares if they are enabled.
237 237 vcs_handler = self.wrap_in_gzip_if_enabled(
238 238 vcs_handler, self.config)
239 239 vcs_handler, _ = wrap_in_appenlight_if_enabled(
240 240 vcs_handler, self.config, self.appenlight_client)
241 241
242 242 return vcs_handler(environ, start_response)
243 243
244 244 return self.application(environ, start_response)
@@ -1,58 +1,59 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 from pyramid.compat import configparser
23 23 from pyramid.paster import bootstrap as pyramid_bootstrap, setup_logging # noqa
24 from pyramid.request import Request
25 24 from pyramid.scripting import prepare
26 25
26 from rhodecode.lib.request import Request
27
27 28
28 29 def get_config(ini_path, **kwargs):
29 30 parser = configparser.ConfigParser(**kwargs)
30 31 parser.read(ini_path)
31 32 return parser
32 33
33 34
34 35 def get_app_config(ini_path):
35 36 from paste.deploy.loadwsgi import appconfig
36 37 return appconfig('config:{}'.format(ini_path), relative_to=os.getcwd())
37 38
38 39
39 40 def bootstrap(config_uri, request=None, options=None, env=None):
40 41 if env:
41 42 os.environ.update(env)
42 43
43 44 config = get_config(config_uri)
44 45 base_url = 'http://rhodecode.local'
45 46 try:
46 47 base_url = config.get('app:main', 'app.base_url')
47 48 except (configparser.NoSectionError, configparser.NoOptionError):
48 49 pass
49 50
50 51 request = request or Request.blank('/', base_url=base_url)
51 52
52 53 return pyramid_bootstrap(config_uri, request=request, options=options)
53 54
54 55
55 56 def prepare_request(environ):
56 57 request = Request.blank('/', environ=environ)
57 58 prepare(request) # set pyramid threadlocal request
58 59 return request
@@ -1,779 +1,779 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 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 Utilities library for RhodeCode
23 23 """
24 24
25 25 import datetime
26 26 import decorator
27 27 import json
28 28 import logging
29 29 import os
30 30 import re
31 31 import shutil
32 32 import tempfile
33 33 import traceback
34 34 import tarfile
35 35 import warnings
36 36 import hashlib
37 37 from os.path import join as jn
38 38
39 39 import paste
40 40 import pkg_resources
41 41 from webhelpers.text import collapse, remove_formatting, strip_tags
42 42 from mako import exceptions
43 43 from pyramid.threadlocal import get_current_registry
44 from pyramid.request import Request
44 from rhodecode.lib.request import Request
45 45
46 46 from rhodecode.lib.fakemod import create_module
47 47 from rhodecode.lib.vcs.backends.base import Config
48 48 from rhodecode.lib.vcs.exceptions import VCSError
49 49 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 50 from rhodecode.lib.utils2 import (
51 51 safe_str, safe_unicode, get_current_rhodecode_user, md5)
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import (
54 54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 55 from rhodecode.model.meta import Session
56 56
57 57
58 58 log = logging.getLogger(__name__)
59 59
60 60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 61
62 62 # String which contains characters that are not allowed in slug names for
63 63 # repositories or repository groups. It is properly escaped to use it in
64 64 # regular expressions.
65 65 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 66
67 67 # Regex that matches forbidden characters in repo/group slugs.
68 68 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69 69
70 70 # Regex that matches allowed characters in repo/group slugs.
71 71 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72 72
73 73 # Regex that matches whole repo/group slugs.
74 74 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75 75
76 76 _license_cache = None
77 77
78 78
79 79 def repo_name_slug(value):
80 80 """
81 81 Return slug of name of repository
82 82 This function is called on each creation/modification
83 83 of repository to prevent bad names in repo
84 84 """
85 85 replacement_char = '-'
86 86
87 87 slug = remove_formatting(value)
88 88 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 89 slug = re.sub('[\s]+', '-', slug)
90 90 slug = collapse(slug, replacement_char)
91 91 return slug
92 92
93 93
94 94 #==============================================================================
95 95 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 96 #==============================================================================
97 97 def get_repo_slug(request):
98 98 _repo = ''
99 99
100 100 if hasattr(request, 'db_repo'):
101 101 # if our requests has set db reference use it for name, this
102 102 # translates the example.com/_<id> into proper repo names
103 103 _repo = request.db_repo.repo_name
104 104 elif getattr(request, 'matchdict', None):
105 105 # pyramid
106 106 _repo = request.matchdict.get('repo_name')
107 107
108 108 if _repo:
109 109 _repo = _repo.rstrip('/')
110 110 return _repo
111 111
112 112
113 113 def get_repo_group_slug(request):
114 114 _group = ''
115 115 if hasattr(request, 'db_repo_group'):
116 116 # if our requests has set db reference use it for name, this
117 117 # translates the example.com/_<id> into proper repo group names
118 118 _group = request.db_repo_group.group_name
119 119 elif getattr(request, 'matchdict', None):
120 120 # pyramid
121 121 _group = request.matchdict.get('repo_group_name')
122 122
123 123
124 124 if _group:
125 125 _group = _group.rstrip('/')
126 126 return _group
127 127
128 128
129 129 def get_user_group_slug(request):
130 130 _user_group = ''
131 131
132 132 if hasattr(request, 'db_user_group'):
133 133 _user_group = request.db_user_group.users_group_name
134 134 elif getattr(request, 'matchdict', None):
135 135 # pyramid
136 136 _user_group = request.matchdict.get('user_group_id')
137 137 _user_group_name = request.matchdict.get('user_group_name')
138 138 try:
139 139 if _user_group:
140 140 _user_group = UserGroup.get(_user_group)
141 141 elif _user_group_name:
142 142 _user_group = UserGroup.get_by_group_name(_user_group_name)
143 143
144 144 if _user_group:
145 145 _user_group = _user_group.users_group_name
146 146 except Exception:
147 147 log.exception('Failed to get user group by id and name')
148 148 # catch all failures here
149 149 return None
150 150
151 151 return _user_group
152 152
153 153
154 154 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
155 155 """
156 156 Scans given path for repos and return (name,(type,path)) tuple
157 157
158 158 :param path: path to scan for repositories
159 159 :param recursive: recursive search and return names with subdirs in front
160 160 """
161 161
162 162 # remove ending slash for better results
163 163 path = path.rstrip(os.sep)
164 164 log.debug('now scanning in %s location recursive:%s...', path, recursive)
165 165
166 166 def _get_repos(p):
167 167 dirpaths = _get_dirpaths(p)
168 168 if not _is_dir_writable(p):
169 169 log.warning('repo path without write access: %s', p)
170 170
171 171 for dirpath in dirpaths:
172 172 if os.path.isfile(os.path.join(p, dirpath)):
173 173 continue
174 174 cur_path = os.path.join(p, dirpath)
175 175
176 176 # skip removed repos
177 177 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
178 178 continue
179 179
180 180 #skip .<somethin> dirs
181 181 if dirpath.startswith('.'):
182 182 continue
183 183
184 184 try:
185 185 scm_info = get_scm(cur_path)
186 186 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
187 187 except VCSError:
188 188 if not recursive:
189 189 continue
190 190 #check if this dir containts other repos for recursive scan
191 191 rec_path = os.path.join(p, dirpath)
192 192 if os.path.isdir(rec_path):
193 193 for inner_scm in _get_repos(rec_path):
194 194 yield inner_scm
195 195
196 196 return _get_repos(path)
197 197
198 198
199 199 def _get_dirpaths(p):
200 200 try:
201 201 # OS-independable way of checking if we have at least read-only
202 202 # access or not.
203 203 dirpaths = os.listdir(p)
204 204 except OSError:
205 205 log.warning('ignoring repo path without read access: %s', p)
206 206 return []
207 207
208 208 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
209 209 # decode paths and suddenly returns unicode objects itself. The items it
210 210 # cannot decode are returned as strings and cause issues.
211 211 #
212 212 # Those paths are ignored here until a solid solution for path handling has
213 213 # been built.
214 214 expected_type = type(p)
215 215
216 216 def _has_correct_type(item):
217 217 if type(item) is not expected_type:
218 218 log.error(
219 219 u"Ignoring path %s since it cannot be decoded into unicode.",
220 220 # Using "repr" to make sure that we see the byte value in case
221 221 # of support.
222 222 repr(item))
223 223 return False
224 224 return True
225 225
226 226 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
227 227
228 228 return dirpaths
229 229
230 230
231 231 def _is_dir_writable(path):
232 232 """
233 233 Probe if `path` is writable.
234 234
235 235 Due to trouble on Cygwin / Windows, this is actually probing if it is
236 236 possible to create a file inside of `path`, stat does not produce reliable
237 237 results in this case.
238 238 """
239 239 try:
240 240 with tempfile.TemporaryFile(dir=path):
241 241 pass
242 242 except OSError:
243 243 return False
244 244 return True
245 245
246 246
247 247 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
248 248 """
249 249 Returns True if given path is a valid repository False otherwise.
250 250 If expect_scm param is given also, compare if given scm is the same
251 251 as expected from scm parameter. If explicit_scm is given don't try to
252 252 detect the scm, just use the given one to check if repo is valid
253 253
254 254 :param repo_name:
255 255 :param base_path:
256 256 :param expect_scm:
257 257 :param explicit_scm:
258 258 :param config:
259 259
260 260 :return True: if given path is a valid repository
261 261 """
262 262 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
263 263 log.debug('Checking if `%s` is a valid path for repository. '
264 264 'Explicit type: %s', repo_name, explicit_scm)
265 265
266 266 try:
267 267 if explicit_scm:
268 268 detected_scms = [get_scm_backend(explicit_scm)(
269 269 full_path, config=config).alias]
270 270 else:
271 271 detected_scms = get_scm(full_path)
272 272
273 273 if expect_scm:
274 274 return detected_scms[0] == expect_scm
275 275 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
276 276 return True
277 277 except VCSError:
278 278 log.debug('path: %s is not a valid repo !', full_path)
279 279 return False
280 280
281 281
282 282 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
283 283 """
284 284 Returns True if given path is a repository group, False otherwise
285 285
286 286 :param repo_name:
287 287 :param base_path:
288 288 """
289 289 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
290 290 log.debug('Checking if `%s` is a valid path for repository group',
291 291 repo_group_name)
292 292
293 293 # check if it's not a repo
294 294 if is_valid_repo(repo_group_name, base_path):
295 295 log.debug('Repo called %s exist, it is not a valid '
296 296 'repo group' % repo_group_name)
297 297 return False
298 298
299 299 try:
300 300 # we need to check bare git repos at higher level
301 301 # since we might match branches/hooks/info/objects or possible
302 302 # other things inside bare git repo
303 303 scm_ = get_scm(os.path.dirname(full_path))
304 304 log.debug('path: %s is a vcs object:%s, not valid '
305 305 'repo group' % (full_path, scm_))
306 306 return False
307 307 except VCSError:
308 308 pass
309 309
310 310 # check if it's a valid path
311 311 if skip_path_check or os.path.isdir(full_path):
312 312 log.debug('path: %s is a valid repo group !', full_path)
313 313 return True
314 314
315 315 log.debug('path: %s is not a valid repo group !', full_path)
316 316 return False
317 317
318 318
319 319 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
320 320 while True:
321 321 ok = raw_input(prompt)
322 322 if ok.lower() in ('y', 'ye', 'yes'):
323 323 return True
324 324 if ok.lower() in ('n', 'no', 'nop', 'nope'):
325 325 return False
326 326 retries = retries - 1
327 327 if retries < 0:
328 328 raise IOError
329 329 print(complaint)
330 330
331 331 # propagated from mercurial documentation
332 332 ui_sections = [
333 333 'alias', 'auth',
334 334 'decode/encode', 'defaults',
335 335 'diff', 'email',
336 336 'extensions', 'format',
337 337 'merge-patterns', 'merge-tools',
338 338 'hooks', 'http_proxy',
339 339 'smtp', 'patch',
340 340 'paths', 'profiling',
341 341 'server', 'trusted',
342 342 'ui', 'web', ]
343 343
344 344
345 345 def config_data_from_db(clear_session=True, repo=None):
346 346 """
347 347 Read the configuration data from the database and return configuration
348 348 tuples.
349 349 """
350 350 from rhodecode.model.settings import VcsSettingsModel
351 351
352 352 config = []
353 353
354 354 sa = meta.Session()
355 355 settings_model = VcsSettingsModel(repo=repo, sa=sa)
356 356
357 357 ui_settings = settings_model.get_ui_settings()
358 358
359 359 ui_data = []
360 360 for setting in ui_settings:
361 361 if setting.active:
362 362 ui_data.append((setting.section, setting.key, setting.value))
363 363 config.append((
364 364 safe_str(setting.section), safe_str(setting.key),
365 365 safe_str(setting.value)))
366 366 if setting.key == 'push_ssl':
367 367 # force set push_ssl requirement to False, rhodecode
368 368 # handles that
369 369 config.append((
370 370 safe_str(setting.section), safe_str(setting.key), False))
371 371 log.debug(
372 372 'settings ui from db: %s',
373 373 ','.join(map(lambda s: '[{}] {}={}'.format(*s), ui_data)))
374 374 if clear_session:
375 375 meta.Session.remove()
376 376
377 377 # TODO: mikhail: probably it makes no sense to re-read hooks information.
378 378 # It's already there and activated/deactivated
379 379 skip_entries = []
380 380 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
381 381 if 'pull' not in enabled_hook_classes:
382 382 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
383 383 if 'push' not in enabled_hook_classes:
384 384 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
385 385 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
386 386 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
387 387
388 388 config = [entry for entry in config if entry[:2] not in skip_entries]
389 389
390 390 return config
391 391
392 392
393 393 def make_db_config(clear_session=True, repo=None):
394 394 """
395 395 Create a :class:`Config` instance based on the values in the database.
396 396 """
397 397 config = Config()
398 398 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
399 399 for section, option, value in config_data:
400 400 config.set(section, option, value)
401 401 return config
402 402
403 403
404 404 def get_enabled_hook_classes(ui_settings):
405 405 """
406 406 Return the enabled hook classes.
407 407
408 408 :param ui_settings: List of ui_settings as returned
409 409 by :meth:`VcsSettingsModel.get_ui_settings`
410 410
411 411 :return: a list with the enabled hook classes. The order is not guaranteed.
412 412 :rtype: list
413 413 """
414 414 enabled_hooks = []
415 415 active_hook_keys = [
416 416 key for section, key, value, active in ui_settings
417 417 if section == 'hooks' and active]
418 418
419 419 hook_names = {
420 420 RhodeCodeUi.HOOK_PUSH: 'push',
421 421 RhodeCodeUi.HOOK_PULL: 'pull',
422 422 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
423 423 }
424 424
425 425 for key in active_hook_keys:
426 426 hook = hook_names.get(key)
427 427 if hook:
428 428 enabled_hooks.append(hook)
429 429
430 430 return enabled_hooks
431 431
432 432
433 433 def set_rhodecode_config(config):
434 434 """
435 435 Updates pyramid config with new settings from database
436 436
437 437 :param config:
438 438 """
439 439 from rhodecode.model.settings import SettingsModel
440 440 app_settings = SettingsModel().get_all_settings()
441 441
442 442 for k, v in app_settings.items():
443 443 config[k] = v
444 444
445 445
446 446 def get_rhodecode_realm():
447 447 """
448 448 Return the rhodecode realm from database.
449 449 """
450 450 from rhodecode.model.settings import SettingsModel
451 451 realm = SettingsModel().get_setting_by_name('realm')
452 452 return safe_str(realm.app_settings_value)
453 453
454 454
455 455 def get_rhodecode_base_path():
456 456 """
457 457 Returns the base path. The base path is the filesystem path which points
458 458 to the repository store.
459 459 """
460 460 from rhodecode.model.settings import SettingsModel
461 461 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
462 462 return safe_str(paths_ui.ui_value)
463 463
464 464
465 465 def map_groups(path):
466 466 """
467 467 Given a full path to a repository, create all nested groups that this
468 468 repo is inside. This function creates parent-child relationships between
469 469 groups and creates default perms for all new groups.
470 470
471 471 :param paths: full path to repository
472 472 """
473 473 from rhodecode.model.repo_group import RepoGroupModel
474 474 sa = meta.Session()
475 475 groups = path.split(Repository.NAME_SEP)
476 476 parent = None
477 477 group = None
478 478
479 479 # last element is repo in nested groups structure
480 480 groups = groups[:-1]
481 481 rgm = RepoGroupModel(sa)
482 482 owner = User.get_first_super_admin()
483 483 for lvl, group_name in enumerate(groups):
484 484 group_name = '/'.join(groups[:lvl] + [group_name])
485 485 group = RepoGroup.get_by_group_name(group_name)
486 486 desc = '%s group' % group_name
487 487
488 488 # skip folders that are now removed repos
489 489 if REMOVED_REPO_PAT.match(group_name):
490 490 break
491 491
492 492 if group is None:
493 493 log.debug('creating group level: %s group_name: %s',
494 494 lvl, group_name)
495 495 group = RepoGroup(group_name, parent)
496 496 group.group_description = desc
497 497 group.user = owner
498 498 sa.add(group)
499 499 perm_obj = rgm._create_default_perms(group)
500 500 sa.add(perm_obj)
501 501 sa.flush()
502 502
503 503 parent = group
504 504 return group
505 505
506 506
507 507 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
508 508 """
509 509 maps all repos given in initial_repo_list, non existing repositories
510 510 are created, if remove_obsolete is True it also checks for db entries
511 511 that are not in initial_repo_list and removes them.
512 512
513 513 :param initial_repo_list: list of repositories found by scanning methods
514 514 :param remove_obsolete: check for obsolete entries in database
515 515 """
516 516 from rhodecode.model.repo import RepoModel
517 517 from rhodecode.model.repo_group import RepoGroupModel
518 518 from rhodecode.model.settings import SettingsModel
519 519
520 520 sa = meta.Session()
521 521 repo_model = RepoModel()
522 522 user = User.get_first_super_admin()
523 523 added = []
524 524
525 525 # creation defaults
526 526 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
527 527 enable_statistics = defs.get('repo_enable_statistics')
528 528 enable_locking = defs.get('repo_enable_locking')
529 529 enable_downloads = defs.get('repo_enable_downloads')
530 530 private = defs.get('repo_private')
531 531
532 532 for name, repo in initial_repo_list.items():
533 533 group = map_groups(name)
534 534 unicode_name = safe_unicode(name)
535 535 db_repo = repo_model.get_by_repo_name(unicode_name)
536 536 # found repo that is on filesystem not in RhodeCode database
537 537 if not db_repo:
538 538 log.info('repository %s not found, creating now', name)
539 539 added.append(name)
540 540 desc = (repo.description
541 541 if repo.description != 'unknown'
542 542 else '%s repository' % name)
543 543
544 544 db_repo = repo_model._create_repo(
545 545 repo_name=name,
546 546 repo_type=repo.alias,
547 547 description=desc,
548 548 repo_group=getattr(group, 'group_id', None),
549 549 owner=user,
550 550 enable_locking=enable_locking,
551 551 enable_downloads=enable_downloads,
552 552 enable_statistics=enable_statistics,
553 553 private=private,
554 554 state=Repository.STATE_CREATED
555 555 )
556 556 sa.commit()
557 557 # we added that repo just now, and make sure we updated server info
558 558 if db_repo.repo_type == 'git':
559 559 git_repo = db_repo.scm_instance()
560 560 # update repository server-info
561 561 log.debug('Running update server info')
562 562 git_repo._update_server_info()
563 563
564 564 db_repo.update_commit_cache()
565 565
566 566 config = db_repo._config
567 567 config.set('extensions', 'largefiles', '')
568 568 repo = db_repo.scm_instance(config=config)
569 569 repo.install_hooks()
570 570
571 571 removed = []
572 572 if remove_obsolete:
573 573 # remove from database those repositories that are not in the filesystem
574 574 for repo in sa.query(Repository).all():
575 575 if repo.repo_name not in initial_repo_list.keys():
576 576 log.debug("Removing non-existing repository found in db `%s`",
577 577 repo.repo_name)
578 578 try:
579 579 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
580 580 sa.commit()
581 581 removed.append(repo.repo_name)
582 582 except Exception:
583 583 # don't hold further removals on error
584 584 log.error(traceback.format_exc())
585 585 sa.rollback()
586 586
587 587 def splitter(full_repo_name):
588 588 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
589 589 gr_name = None
590 590 if len(_parts) == 2:
591 591 gr_name = _parts[0]
592 592 return gr_name
593 593
594 594 initial_repo_group_list = [splitter(x) for x in
595 595 initial_repo_list.keys() if splitter(x)]
596 596
597 597 # remove from database those repository groups that are not in the
598 598 # filesystem due to parent child relationships we need to delete them
599 599 # in a specific order of most nested first
600 600 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
601 601 nested_sort = lambda gr: len(gr.split('/'))
602 602 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
603 603 if group_name not in initial_repo_group_list:
604 604 repo_group = RepoGroup.get_by_group_name(group_name)
605 605 if (repo_group.children.all() or
606 606 not RepoGroupModel().check_exist_filesystem(
607 607 group_name=group_name, exc_on_failure=False)):
608 608 continue
609 609
610 610 log.info(
611 611 'Removing non-existing repository group found in db `%s`',
612 612 group_name)
613 613 try:
614 614 RepoGroupModel(sa).delete(group_name, fs_remove=False)
615 615 sa.commit()
616 616 removed.append(group_name)
617 617 except Exception:
618 618 # don't hold further removals on error
619 619 log.exception(
620 620 'Unable to remove repository group `%s`',
621 621 group_name)
622 622 sa.rollback()
623 623 raise
624 624
625 625 return added, removed
626 626
627 627
628 628 def load_rcextensions(root_path):
629 629 import rhodecode
630 630 from rhodecode.config import conf
631 631
632 632 path = os.path.join(root_path, 'rcextensions', '__init__.py')
633 633 if os.path.isfile(path):
634 634 rcext = create_module('rc', path)
635 635 EXT = rhodecode.EXTENSIONS = rcext
636 636 log.debug('Found rcextensions now loading %s...', rcext)
637 637
638 638 # Additional mappings that are not present in the pygments lexers
639 639 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
640 640
641 641 # auto check if the module is not missing any data, set to default if is
642 642 # this will help autoupdate new feature of rcext module
643 643 #from rhodecode.config import rcextensions
644 644 #for k in dir(rcextensions):
645 645 # if not k.startswith('_') and not hasattr(EXT, k):
646 646 # setattr(EXT, k, getattr(rcextensions, k))
647 647
648 648
649 649 def get_custom_lexer(extension):
650 650 """
651 651 returns a custom lexer if it is defined in rcextensions module, or None
652 652 if there's no custom lexer defined
653 653 """
654 654 import rhodecode
655 655 from pygments import lexers
656 656
657 657 # custom override made by RhodeCode
658 658 if extension in ['mako']:
659 659 return lexers.get_lexer_by_name('html+mako')
660 660
661 661 # check if we didn't define this extension as other lexer
662 662 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
663 663 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
664 664 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
665 665 return lexers.get_lexer_by_name(_lexer_name)
666 666
667 667
668 668 #==============================================================================
669 669 # TEST FUNCTIONS AND CREATORS
670 670 #==============================================================================
671 671 def create_test_index(repo_location, config):
672 672 """
673 673 Makes default test index.
674 674 """
675 675 import rc_testdata
676 676
677 677 rc_testdata.extract_search_index(
678 678 'vcs_search_index', os.path.dirname(config['search.location']))
679 679
680 680
681 681 def create_test_directory(test_path):
682 682 """
683 683 Create test directory if it doesn't exist.
684 684 """
685 685 if not os.path.isdir(test_path):
686 686 log.debug('Creating testdir %s', test_path)
687 687 os.makedirs(test_path)
688 688
689 689
690 690 def create_test_database(test_path, config):
691 691 """
692 692 Makes a fresh database.
693 693 """
694 694 from rhodecode.lib.db_manage import DbManage
695 695
696 696 # PART ONE create db
697 697 dbconf = config['sqlalchemy.db1.url']
698 698 log.debug('making test db %s', dbconf)
699 699
700 700 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
701 701 tests=True, cli_args={'force_ask': True})
702 702 dbmanage.create_tables(override=True)
703 703 dbmanage.set_db_version()
704 704 # for tests dynamically set new root paths based on generated content
705 705 dbmanage.create_settings(dbmanage.config_prompt(test_path))
706 706 dbmanage.create_default_user()
707 707 dbmanage.create_test_admin_and_users()
708 708 dbmanage.create_permissions()
709 709 dbmanage.populate_default_permissions()
710 710 Session().commit()
711 711
712 712
713 713 def create_test_repositories(test_path, config):
714 714 """
715 715 Creates test repositories in the temporary directory. Repositories are
716 716 extracted from archives within the rc_testdata package.
717 717 """
718 718 import rc_testdata
719 719 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
720 720
721 721 log.debug('making test vcs repositories')
722 722
723 723 idx_path = config['search.location']
724 724 data_path = config['cache_dir']
725 725
726 726 # clean index and data
727 727 if idx_path and os.path.exists(idx_path):
728 728 log.debug('remove %s', idx_path)
729 729 shutil.rmtree(idx_path)
730 730
731 731 if data_path and os.path.exists(data_path):
732 732 log.debug('remove %s', data_path)
733 733 shutil.rmtree(data_path)
734 734
735 735 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
736 736 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
737 737
738 738 # Note: Subversion is in the process of being integrated with the system,
739 739 # until we have a properly packed version of the test svn repository, this
740 740 # tries to copy over the repo from a package "rc_testdata"
741 741 svn_repo_path = rc_testdata.get_svn_repo_archive()
742 742 with tarfile.open(svn_repo_path) as tar:
743 743 tar.extractall(jn(test_path, SVN_REPO))
744 744
745 745
746 746 def password_changed(auth_user, session):
747 747 # Never report password change in case of default user or anonymous user.
748 748 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
749 749 return False
750 750
751 751 password_hash = md5(auth_user.password) if auth_user.password else None
752 752 rhodecode_user = session.get('rhodecode_user', {})
753 753 session_password_hash = rhodecode_user.get('password', '')
754 754 return password_hash != session_password_hash
755 755
756 756
757 757 def read_opensource_licenses():
758 758 global _license_cache
759 759
760 760 if not _license_cache:
761 761 licenses = pkg_resources.resource_string(
762 762 'rhodecode', 'config/licenses.json')
763 763 _license_cache = json.loads(licenses)
764 764
765 765 return _license_cache
766 766
767 767
768 768 def generate_platform_uuid():
769 769 """
770 770 Generates platform UUID based on it's name
771 771 """
772 772 import platform
773 773
774 774 try:
775 775 uuid_list = [platform.platform()]
776 776 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
777 777 except Exception as e:
778 778 log.error('Failed to generate host uuid: %s' % e)
779 779 return 'UNDEFINED'
@@ -1,322 +1,324 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 import io
21 21 import re
22 22 import datetime
23 23 import logging
24 24 import Queue
25 25 import subprocess32
26 26 import os
27 27
28 28
29 29 from dateutil.parser import parse
30 30 from pyramid.i18n import get_localizer
31 31 from pyramid.threadlocal import get_current_request
32 32 from pyramid.interfaces import IRoutesMapper
33 33 from pyramid.settings import asbool
34 34 from pyramid.path import AssetResolver
35 35 from threading import Thread
36 36
37 37 from rhodecode.translation import _ as tsf
38 38 from rhodecode.config.jsroutes import generate_jsroutes_content
39 39 from rhodecode.lib import auth
40 40 from rhodecode.lib.base import get_auth_user
41 41
42 42
43 43 import rhodecode
44 44
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 def add_renderer_globals(event):
50 50 from rhodecode.lib import helpers
51 51
52 52 # TODO: When executed in pyramid view context the request is not available
53 53 # in the event. Find a better solution to get the request.
54 54 request = event['request'] or get_current_request()
55 55
56 56 # Add Pyramid translation as '_' to context
57 57 event['_'] = request.translate
58 58 event['_ungettext'] = request.plularize
59 59 event['h'] = helpers
60 60
61 61
62 62 def add_localizer(event):
63 63 request = event.request
64 64 localizer = request.localizer
65 65
66 66 def auto_translate(*args, **kwargs):
67 67 return localizer.translate(tsf(*args, **kwargs))
68 68
69 69 request.translate = auto_translate
70 70 request.plularize = localizer.pluralize
71 71
72 72
73 73 def set_user_lang(event):
74 74 request = event.request
75 75 cur_user = getattr(request, 'user', None)
76 76
77 77 if cur_user:
78 78 user_lang = cur_user.get_instance().user_data.get('language')
79 79 if user_lang:
80 80 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
81 81 event.request._LOCALE_ = user_lang
82 82
83 83
84 84 def add_request_user_context(event):
85 85 """
86 86 Adds auth user into request context
87 87 """
88 88 request = event.request
89 # access req_id as soon as possible
90 req_id = request.req_id
89 91
90 92 if hasattr(request, 'vcs_call'):
91 93 # skip vcs calls
92 94 return
93 95
94 96 if hasattr(request, 'rpc_method'):
95 97 # skip api calls
96 98 return
97 99
98 100 auth_user = get_auth_user(request)
99 101 request.user = auth_user
100 102 request.environ['rc_auth_user'] = auth_user
101
103 request.environ['rc_req_id'] = req_id
102 104
103 105 def inject_app_settings(event):
104 106 settings = event.app.registry.settings
105 107 # inject info about available permissions
106 108 auth.set_available_permissions(settings)
107 109
108 110
109 111 def scan_repositories_if_enabled(event):
110 112 """
111 113 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
112 114 does a repository scan if enabled in the settings.
113 115 """
114 116 settings = event.app.registry.settings
115 117 vcs_server_enabled = settings['vcs.server.enable']
116 118 import_on_startup = settings['startup.import_repos']
117 119 if vcs_server_enabled and import_on_startup:
118 120 from rhodecode.model.scm import ScmModel
119 121 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
120 122 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
121 123 repo2db_mapper(repositories, remove_obsolete=False)
122 124
123 125
124 126 def write_metadata_if_needed(event):
125 127 """
126 128 Writes upgrade metadata
127 129 """
128 130 import rhodecode
129 131 from rhodecode.lib import system_info
130 132 from rhodecode.lib import ext_json
131 133
132 134 fname = '.rcmetadata.json'
133 135 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
134 136 metadata_destination = os.path.join(ini_loc, fname)
135 137
136 138 def get_update_age():
137 139 now = datetime.datetime.utcnow()
138 140
139 141 with open(metadata_destination, 'rb') as f:
140 142 data = ext_json.json.loads(f.read())
141 143 if 'created_on' in data:
142 144 update_date = parse(data['created_on'])
143 145 diff = now - update_date
144 146 return diff.total_seconds() / 60.0
145 147
146 148 return 0
147 149
148 150 def write():
149 151 configuration = system_info.SysInfo(
150 152 system_info.rhodecode_config)()['value']
151 153 license_token = configuration['config']['license_token']
152 154
153 155 setup = dict(
154 156 workers=configuration['config']['server:main'].get(
155 157 'workers', '?'),
156 158 worker_type=configuration['config']['server:main'].get(
157 159 'worker_class', 'sync'),
158 160 )
159 161 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
160 162 del dbinfo['url']
161 163
162 164 metadata = dict(
163 165 desc='upgrade metadata info',
164 166 license_token=license_token,
165 167 created_on=datetime.datetime.utcnow().isoformat(),
166 168 usage=system_info.SysInfo(system_info.usage_info)()['value'],
167 169 platform=system_info.SysInfo(system_info.platform_type)()['value'],
168 170 database=dbinfo,
169 171 cpu=system_info.SysInfo(system_info.cpu)()['value'],
170 172 memory=system_info.SysInfo(system_info.memory)()['value'],
171 173 setup=setup
172 174 )
173 175
174 176 with open(metadata_destination, 'wb') as f:
175 177 f.write(ext_json.json.dumps(metadata))
176 178
177 179 settings = event.app.registry.settings
178 180 if settings.get('metadata.skip'):
179 181 return
180 182
181 183 # only write this every 24h, workers restart caused unwanted delays
182 184 try:
183 185 age_in_min = get_update_age()
184 186 except Exception:
185 187 age_in_min = 0
186 188
187 189 if age_in_min > 60 * 60 * 24:
188 190 return
189 191
190 192 try:
191 193 write()
192 194 except Exception:
193 195 pass
194 196
195 197
196 198 def write_js_routes_if_enabled(event):
197 199 registry = event.app.registry
198 200
199 201 mapper = registry.queryUtility(IRoutesMapper)
200 202 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
201 203
202 204 def _extract_route_information(route):
203 205 """
204 206 Convert a route into tuple(name, path, args), eg:
205 207 ('show_user', '/profile/%(username)s', ['username'])
206 208 """
207 209
208 210 routepath = route.pattern
209 211 pattern = route.pattern
210 212
211 213 def replace(matchobj):
212 214 if matchobj.group(1):
213 215 return "%%(%s)s" % matchobj.group(1).split(':')[0]
214 216 else:
215 217 return "%%(%s)s" % matchobj.group(2)
216 218
217 219 routepath = _argument_prog.sub(replace, routepath)
218 220
219 221 if not routepath.startswith('/'):
220 222 routepath = '/'+routepath
221 223
222 224 return (
223 225 route.name,
224 226 routepath,
225 227 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
226 228 for arg in _argument_prog.findall(pattern)]
227 229 )
228 230
229 231 def get_routes():
230 232 # pyramid routes
231 233 for route in mapper.get_routes():
232 234 if not route.name.startswith('__'):
233 235 yield _extract_route_information(route)
234 236
235 237 if asbool(registry.settings.get('generate_js_files', 'false')):
236 238 static_path = AssetResolver().resolve('rhodecode:public').abspath()
237 239 jsroutes = get_routes()
238 240 jsroutes_file_content = generate_jsroutes_content(jsroutes)
239 241 jsroutes_file_path = os.path.join(
240 242 static_path, 'js', 'rhodecode', 'routes.js')
241 243
242 244 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
243 245 f.write(jsroutes_file_content)
244 246
245 247
246 248 class Subscriber(object):
247 249 """
248 250 Base class for subscribers to the pyramid event system.
249 251 """
250 252 def __call__(self, event):
251 253 self.run(event)
252 254
253 255 def run(self, event):
254 256 raise NotImplementedError('Subclass has to implement this.')
255 257
256 258
257 259 class AsyncSubscriber(Subscriber):
258 260 """
259 261 Subscriber that handles the execution of events in a separate task to not
260 262 block the execution of the code which triggers the event. It puts the
261 263 received events into a queue from which the worker process takes them in
262 264 order.
263 265 """
264 266 def __init__(self):
265 267 self._stop = False
266 268 self._eventq = Queue.Queue()
267 269 self._worker = self.create_worker()
268 270 self._worker.start()
269 271
270 272 def __call__(self, event):
271 273 self._eventq.put(event)
272 274
273 275 def create_worker(self):
274 276 worker = Thread(target=self.do_work)
275 277 worker.daemon = True
276 278 return worker
277 279
278 280 def stop_worker(self):
279 281 self._stop = False
280 282 self._eventq.put(None)
281 283 self._worker.join()
282 284
283 285 def do_work(self):
284 286 while not self._stop:
285 287 event = self._eventq.get()
286 288 if event is not None:
287 289 self.run(event)
288 290
289 291
290 292 class AsyncSubprocessSubscriber(AsyncSubscriber):
291 293 """
292 294 Subscriber that uses the subprocess32 module to execute a command if an
293 295 event is received. Events are handled asynchronously.
294 296 """
295 297
296 298 def __init__(self, cmd, timeout=None):
297 299 super(AsyncSubprocessSubscriber, self).__init__()
298 300 self._cmd = cmd
299 301 self._timeout = timeout
300 302
301 303 def run(self, event):
302 304 cmd = self._cmd
303 305 timeout = self._timeout
304 306 log.debug('Executing command %s.', cmd)
305 307
306 308 try:
307 309 output = subprocess32.check_output(
308 310 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
309 311 log.debug('Command finished %s', cmd)
310 312 if output:
311 313 log.debug('Command output: %s', output)
312 314 except subprocess32.TimeoutExpired as e:
313 315 log.exception('Timeout while executing command.')
314 316 if e.output:
315 317 log.error('Command output: %s', e.output)
316 318 except subprocess32.CalledProcessError as e:
317 319 log.exception('Error while executing command.')
318 320 if e.output:
319 321 log.error('Command output: %s', e.output)
320 322 except:
321 323 log.exception(
322 324 'Exception while executing command %s.', cmd)
General Comments 0
You need to be logged in to leave comments. Login now