##// END OF EJS Templates
core: added ops view to pyramid to have a PING command with pure pyramid.
marcink -
r1669:f7580c8e default
parent child Browse files
Show More
@@ -0,0 +1,35 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 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 rhodecode.config.routing import ADMIN_PREFIX
22
23
24 def admin_routes(config):
25 config.add_route(
26 name='ops_ping',
27 pattern='/ping')
28
29
30 def includeme(config):
31
32 config.include(admin_routes, route_prefix=ADMIN_PREFIX + '/ops')
33
34 # Scan module for configuration decorators.
35 config.scan()
@@ -0,0 +1,54 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 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 import logging
22
23 from pyramid.view import view_config
24
25 from rhodecode.apps._base import BaseAppView
26
27
28 log = logging.getLogger(__name__)
29
30
31 class OpsView(BaseAppView):
32
33 def load_default_context(self):
34 c = self._get_local_tmpl_context()
35 c.user = c.auth_user.get_instance()
36 self._register_global_c(c)
37 return c
38
39 @view_config(
40 route_name='ops_ping', request_method='GET',
41 renderer='json_ext')
42 def ops_ping(self):
43 data = {
44 'instance': self.request.registry.settings.get('instance_id'),
45 }
46 if getattr(self.request, 'user'):
47 data.update({
48 'caller_ip': self.request.user.ip_addr,
49 'caller_name': self.request.user.username,
50 })
51 return {'ok': data}
52
53
54
@@ -1,513 +1,514 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Pylons middleware initialization
23 23 """
24 24 import logging
25 25 from collections import OrderedDict
26 26
27 27 from paste.registry import RegistryManager
28 28 from paste.gzipper import make_gzip_middleware
29 29 from pylons.wsgiapp import PylonsApp
30 30 from pyramid.authorization import ACLAuthorizationPolicy
31 31 from pyramid.config import Configurator
32 32 from pyramid.settings import asbool, aslist
33 33 from pyramid.wsgi import wsgiapp
34 34 from pyramid.httpexceptions import (
35 35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
36 36 from pyramid.events import ApplicationCreated
37 37 from pyramid.renderers import render_to_response
38 38 from routes.middleware import RoutesMiddleware
39 39 import routes.util
40 40
41 41 import rhodecode
42 42 from rhodecode.model import meta
43 43 from rhodecode.config import patches
44 44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 45 from rhodecode.config.environment import (
46 46 load_environment, load_pyramid_environment)
47 47 from rhodecode.lib.middleware import csrf
48 48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 49 from rhodecode.lib.middleware.error_handling import (
50 50 PylonsErrorHandlingMiddleware)
51 51 from rhodecode.lib.middleware.https_fixup import HttpsFixup
52 52 from rhodecode.lib.middleware.vcs import VCSMiddleware
53 53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
54 54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
55 55 from rhodecode.subscribers import (
56 56 scan_repositories_if_enabled, write_metadata_if_needed,
57 57 write_js_routes_if_enabled, create_largeobjects_dirs_if_needed)
58 58
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 # this is used to avoid avoid the route lookup overhead in routesmiddleware
64 64 # for certain routes which won't go to pylons to - eg. static files, debugger
65 65 # it is only needed for the pylons migration and can be removed once complete
66 66 class SkippableRoutesMiddleware(RoutesMiddleware):
67 67 """ Routes middleware that allows you to skip prefixes """
68 68
69 69 def __init__(self, *args, **kw):
70 70 self.skip_prefixes = kw.pop('skip_prefixes', [])
71 71 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
72 72
73 73 def __call__(self, environ, start_response):
74 74 for prefix in self.skip_prefixes:
75 75 if environ['PATH_INFO'].startswith(prefix):
76 76 # added to avoid the case when a missing /_static route falls
77 77 # through to pylons and causes an exception as pylons is
78 78 # expecting wsgiorg.routingargs to be set in the environ
79 79 # by RoutesMiddleware.
80 80 if 'wsgiorg.routing_args' not in environ:
81 81 environ['wsgiorg.routing_args'] = (None, {})
82 82 return self.app(environ, start_response)
83 83
84 84 return super(SkippableRoutesMiddleware, self).__call__(
85 85 environ, start_response)
86 86
87 87
88 88 def make_app(global_conf, static_files=True, **app_conf):
89 89 """Create a Pylons WSGI application and return it
90 90
91 91 ``global_conf``
92 92 The inherited configuration for this application. Normally from
93 93 the [DEFAULT] section of the Paste ini file.
94 94
95 95 ``app_conf``
96 96 The application's local configuration. Normally specified in
97 97 the [app:<name>] section of the Paste ini file (where <name>
98 98 defaults to main).
99 99
100 100 """
101 101 # Apply compatibility patches
102 102 patches.kombu_1_5_1_python_2_7_11()
103 103 patches.inspect_getargspec()
104 104
105 105 # Configure the Pylons environment
106 106 config = load_environment(global_conf, app_conf)
107 107
108 108 # The Pylons WSGI app
109 109 app = PylonsApp(config=config)
110 110 if rhodecode.is_test:
111 111 app = csrf.CSRFDetector(app)
112 112
113 113 expected_origin = config.get('expected_origin')
114 114 if expected_origin:
115 115 # The API can be accessed from other Origins.
116 116 app = csrf.OriginChecker(app, expected_origin,
117 117 skip_urls=[routes.util.url_for('api')])
118 118
119 119 # Establish the Registry for this application
120 120 app = RegistryManager(app)
121 121
122 122 app.config = config
123 123
124 124 return app
125 125
126 126
127 127 def make_pyramid_app(global_config, **settings):
128 128 """
129 129 Constructs the WSGI application based on Pyramid and wraps the Pylons based
130 130 application.
131 131
132 132 Specials:
133 133
134 134 * We migrate from Pylons to Pyramid. While doing this, we keep both
135 135 frameworks functional. This involves moving some WSGI middlewares around
136 136 and providing access to some data internals, so that the old code is
137 137 still functional.
138 138
139 139 * The application can also be integrated like a plugin via the call to
140 140 `includeme`. This is accompanied with the other utility functions which
141 141 are called. Changing this should be done with great care to not break
142 142 cases when these fragments are assembled from another place.
143 143
144 144 """
145 145 # The edition string should be available in pylons too, so we add it here
146 146 # before copying the settings.
147 147 settings.setdefault('rhodecode.edition', 'Community Edition')
148 148
149 149 # As long as our Pylons application does expect "unprepared" settings, make
150 150 # sure that we keep an unmodified copy. This avoids unintentional change of
151 151 # behavior in the old application.
152 152 settings_pylons = settings.copy()
153 153
154 154 sanitize_settings_and_apply_defaults(settings)
155 155 config = Configurator(settings=settings)
156 156 add_pylons_compat_data(config.registry, global_config, settings_pylons)
157 157
158 158 load_pyramid_environment(global_config, settings)
159 159
160 160 includeme_first(config)
161 161 includeme(config)
162 162 pyramid_app = config.make_wsgi_app()
163 163 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
164 164 pyramid_app.config = config
165 165
166 166 # creating the app uses a connection - return it after we are done
167 167 meta.Session.remove()
168 168
169 169 return pyramid_app
170 170
171 171
172 172 def make_not_found_view(config):
173 173 """
174 174 This creates the view which should be registered as not-found-view to
175 175 pyramid. Basically it contains of the old pylons app, converted to a view.
176 176 Additionally it is wrapped by some other middlewares.
177 177 """
178 178 settings = config.registry.settings
179 179 vcs_server_enabled = settings['vcs.server.enable']
180 180
181 181 # Make pylons app from unprepared settings.
182 182 pylons_app = make_app(
183 183 config.registry._pylons_compat_global_config,
184 184 **config.registry._pylons_compat_settings)
185 185 config.registry._pylons_compat_config = pylons_app.config
186 186
187 187 # Appenlight monitoring.
188 188 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
189 189 pylons_app, settings)
190 190
191 191 # The pylons app is executed inside of the pyramid 404 exception handler.
192 192 # Exceptions which are raised inside of it are not handled by pyramid
193 193 # again. Therefore we add a middleware that invokes the error handler in
194 194 # case of an exception or error response. This way we return proper error
195 195 # HTML pages in case of an error.
196 196 reraise = (settings.get('debugtoolbar.enabled', False) or
197 197 rhodecode.disable_error_handler)
198 198 pylons_app = PylonsErrorHandlingMiddleware(
199 199 pylons_app, error_handler, reraise)
200 200
201 201 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
202 202 # view to handle the request. Therefore it is wrapped around the pylons
203 203 # app. It has to be outside of the error handling otherwise error responses
204 204 # from the vcsserver are converted to HTML error pages. This confuses the
205 205 # command line tools and the user won't get a meaningful error message.
206 206 if vcs_server_enabled:
207 207 pylons_app = VCSMiddleware(
208 208 pylons_app, settings, appenlight_client, registry=config.registry)
209 209
210 210 # Convert WSGI app to pyramid view and return it.
211 211 return wsgiapp(pylons_app)
212 212
213 213
214 214 def add_pylons_compat_data(registry, global_config, settings):
215 215 """
216 216 Attach data to the registry to support the Pylons integration.
217 217 """
218 218 registry._pylons_compat_global_config = global_config
219 219 registry._pylons_compat_settings = settings
220 220
221 221
222 222 def error_handler(exception, request):
223 223 import rhodecode
224 224 from rhodecode.lib.utils2 import AttributeDict
225 225
226 226 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
227 227
228 228 base_response = HTTPInternalServerError()
229 229 # prefer original exception for the response since it may have headers set
230 230 if isinstance(exception, HTTPException):
231 231 base_response = exception
232 232
233 233 def is_http_error(response):
234 234 # error which should have traceback
235 235 return response.status_code > 499
236 236
237 237 if is_http_error(base_response):
238 238 log.exception(
239 239 'error occurred handling this request for path: %s', request.path)
240 240
241 241 c = AttributeDict()
242 242 c.error_message = base_response.status
243 243 c.error_explanation = base_response.explanation or str(base_response)
244 244 c.visual = AttributeDict()
245 245
246 246 c.visual.rhodecode_support_url = (
247 247 request.registry.settings.get('rhodecode_support_url') or
248 248 request.route_url('rhodecode_support')
249 249 )
250 250 c.redirect_time = 0
251 251 c.rhodecode_name = rhodecode_title
252 252 if not c.rhodecode_name:
253 253 c.rhodecode_name = 'Rhodecode'
254 254
255 255 c.causes = []
256 256 if hasattr(base_response, 'causes'):
257 257 c.causes = base_response.causes
258 258
259 259 response = render_to_response(
260 260 '/errors/error_document.mako', {'c': c}, request=request,
261 261 response=base_response)
262 262
263 263 return response
264 264
265 265
266 266 def includeme(config):
267 267 settings = config.registry.settings
268 268
269 269 # plugin information
270 270 config.registry.rhodecode_plugins = OrderedDict()
271 271
272 272 config.add_directive(
273 273 'register_rhodecode_plugin', register_rhodecode_plugin)
274 274
275 275 if asbool(settings.get('appenlight', 'false')):
276 276 config.include('appenlight_client.ext.pyramid_tween')
277 277
278 278 # Includes which are required. The application would fail without them.
279 279 config.include('pyramid_mako')
280 280 config.include('pyramid_beaker')
281 281
282 282 config.include('rhodecode.authentication')
283 283 config.include('rhodecode.integrations')
284 284
285 285 # apps
286 286 config.include('rhodecode.apps._base')
287 config.include('rhodecode.apps.ops')
287 288
288 289 config.include('rhodecode.apps.admin')
289 290 config.include('rhodecode.apps.channelstream')
290 291 config.include('rhodecode.apps.login')
291 292 config.include('rhodecode.apps.home')
292 293 config.include('rhodecode.apps.repository')
293 294 config.include('rhodecode.apps.user_profile')
294 295 config.include('rhodecode.apps.my_account')
295 296 config.include('rhodecode.apps.svn_support')
296 297
297 298 config.include('rhodecode.tweens')
298 299 config.include('rhodecode.api')
299 300
300 301 config.add_route(
301 302 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
302 303
303 304 config.add_translation_dirs('rhodecode:i18n/')
304 305 settings['default_locale_name'] = settings.get('lang', 'en')
305 306
306 307 # Add subscribers.
307 308 config.add_subscriber(create_largeobjects_dirs_if_needed, ApplicationCreated)
308 309 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
309 310 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
310 311 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
311 312
312 313 # Set the authorization policy.
313 314 authz_policy = ACLAuthorizationPolicy()
314 315 config.set_authorization_policy(authz_policy)
315 316
316 317 # Set the default renderer for HTML templates to mako.
317 318 config.add_mako_renderer('.html')
318 319
319 320 config.add_renderer(
320 321 name='json_ext',
321 322 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
322 323
323 324 # include RhodeCode plugins
324 325 includes = aslist(settings.get('rhodecode.includes', []))
325 326 for inc in includes:
326 327 config.include(inc)
327 328
328 329 # This is the glue which allows us to migrate in chunks. By registering the
329 330 # pylons based application as the "Not Found" view in Pyramid, we will
330 331 # fallback to the old application each time the new one does not yet know
331 332 # how to handle a request.
332 333 config.add_notfound_view(make_not_found_view(config))
333 334
334 335 if not settings.get('debugtoolbar.enabled', False):
335 336 # if no toolbar, then any exception gets caught and rendered
336 337 config.add_view(error_handler, context=Exception)
337 338
338 339 config.add_view(error_handler, context=HTTPError)
339 340
340 341
341 342 def includeme_first(config):
342 343 # redirect automatic browser favicon.ico requests to correct place
343 344 def favicon_redirect(context, request):
344 345 return HTTPFound(
345 346 request.static_path('rhodecode:public/images/favicon.ico'))
346 347
347 348 config.add_view(favicon_redirect, route_name='favicon')
348 349 config.add_route('favicon', '/favicon.ico')
349 350
350 351 def robots_redirect(context, request):
351 352 return HTTPFound(
352 353 request.static_path('rhodecode:public/robots.txt'))
353 354
354 355 config.add_view(robots_redirect, route_name='robots')
355 356 config.add_route('robots', '/robots.txt')
356 357
357 358 config.add_static_view(
358 359 '_static/deform', 'deform:static')
359 360 config.add_static_view(
360 361 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
361 362
362 363
363 364 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
364 365 """
365 366 Apply outer WSGI middlewares around the application.
366 367
367 368 Part of this has been moved up from the Pylons layer, so that the
368 369 data is also available if old Pylons code is hit through an already ported
369 370 view.
370 371 """
371 372 settings = config.registry.settings
372 373
373 374 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
374 375 pyramid_app = HttpsFixup(pyramid_app, settings)
375 376
376 377 # Add RoutesMiddleware to support the pylons compatibility tween during
377 378 # migration to pyramid.
378 379 pyramid_app = SkippableRoutesMiddleware(
379 380 pyramid_app, config.registry._pylons_compat_config['routes.map'],
380 381 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
381 382
382 383 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
383 384
384 385 if settings['gzip_responses']:
385 386 pyramid_app = make_gzip_middleware(
386 387 pyramid_app, settings, compress_level=1)
387 388
388 389 # this should be the outer most middleware in the wsgi stack since
389 390 # middleware like Routes make database calls
390 391 def pyramid_app_with_cleanup(environ, start_response):
391 392 try:
392 393 return pyramid_app(environ, start_response)
393 394 finally:
394 395 # Dispose current database session and rollback uncommitted
395 396 # transactions.
396 397 meta.Session.remove()
397 398
398 399 # In a single threaded mode server, on non sqlite db we should have
399 400 # '0 Current Checked out connections' at the end of a request,
400 401 # if not, then something, somewhere is leaving a connection open
401 402 pool = meta.Base.metadata.bind.engine.pool
402 403 log.debug('sa pool status: %s', pool.status())
403 404
404 405
405 406 return pyramid_app_with_cleanup
406 407
407 408
408 409 def sanitize_settings_and_apply_defaults(settings):
409 410 """
410 411 Applies settings defaults and does all type conversion.
411 412
412 413 We would move all settings parsing and preparation into this place, so that
413 414 we have only one place left which deals with this part. The remaining parts
414 415 of the application would start to rely fully on well prepared settings.
415 416
416 417 This piece would later be split up per topic to avoid a big fat monster
417 418 function.
418 419 """
419 420
420 421 # Pyramid's mako renderer has to search in the templates folder so that the
421 422 # old templates still work. Ported and new templates are expected to use
422 423 # real asset specifications for the includes.
423 424 mako_directories = settings.setdefault('mako.directories', [
424 425 # Base templates of the original Pylons application
425 426 'rhodecode:templates',
426 427 ])
427 428 log.debug(
428 429 "Using the following Mako template directories: %s",
429 430 mako_directories)
430 431
431 432 # Default includes, possible to change as a user
432 433 pyramid_includes = settings.setdefault('pyramid.includes', [
433 434 'rhodecode.lib.middleware.request_wrapper',
434 435 ])
435 436 log.debug(
436 437 "Using the following pyramid.includes: %s",
437 438 pyramid_includes)
438 439
439 440 # TODO: johbo: Re-think this, usually the call to config.include
440 441 # should allow to pass in a prefix.
441 442 settings.setdefault('rhodecode.api.url', '/_admin/api')
442 443
443 444 # Sanitize generic settings.
444 445 _list_setting(settings, 'default_encoding', 'UTF-8')
445 446 _bool_setting(settings, 'is_test', 'false')
446 447 _bool_setting(settings, 'gzip_responses', 'false')
447 448
448 449 # Call split out functions that sanitize settings for each topic.
449 450 _sanitize_appenlight_settings(settings)
450 451 _sanitize_vcs_settings(settings)
451 452
452 453 return settings
453 454
454 455
455 456 def _sanitize_appenlight_settings(settings):
456 457 _bool_setting(settings, 'appenlight', 'false')
457 458
458 459
459 460 def _sanitize_vcs_settings(settings):
460 461 """
461 462 Applies settings defaults and does type conversion for all VCS related
462 463 settings.
463 464 """
464 465 _string_setting(settings, 'vcs.svn.compatible_version', '')
465 466 _string_setting(settings, 'git_rev_filter', '--all')
466 467 _string_setting(settings, 'vcs.hooks.protocol', 'http')
467 468 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
468 469 _string_setting(settings, 'vcs.server', '')
469 470 _string_setting(settings, 'vcs.server.log_level', 'debug')
470 471 _string_setting(settings, 'vcs.server.protocol', 'http')
471 472 _bool_setting(settings, 'startup.import_repos', 'false')
472 473 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
473 474 _bool_setting(settings, 'vcs.server.enable', 'true')
474 475 _bool_setting(settings, 'vcs.start_server', 'false')
475 476 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
476 477 _int_setting(settings, 'vcs.connection_timeout', 3600)
477 478
478 479 # Support legacy values of vcs.scm_app_implementation. Legacy
479 480 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
480 481 # which is now mapped to 'http'.
481 482 scm_app_impl = settings['vcs.scm_app_implementation']
482 483 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
483 484 settings['vcs.scm_app_implementation'] = 'http'
484 485
485 486
486 487 def _int_setting(settings, name, default):
487 488 settings[name] = int(settings.get(name, default))
488 489
489 490
490 491 def _bool_setting(settings, name, default):
491 492 input = settings.get(name, default)
492 493 if isinstance(input, unicode):
493 494 input = input.encode('utf8')
494 495 settings[name] = asbool(input)
495 496
496 497
497 498 def _list_setting(settings, name, default):
498 499 raw_value = settings.get(name, default)
499 500
500 501 old_separator = ','
501 502 if old_separator in raw_value:
502 503 # If we get a comma separated list, pass it to our own function.
503 504 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
504 505 else:
505 506 # Otherwise we assume it uses pyramids space/newline separation.
506 507 settings[name] = aslist(raw_value)
507 508
508 509
509 510 def _string_setting(settings, name, default, lower=True):
510 511 value = settings.get(name, default)
511 512 if lower:
512 513 value = value.lower()
513 514 settings[name] = value
@@ -1,776 +1,777 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import hashlib
22 22 import logging
23 23 from collections import namedtuple
24 24 from functools import wraps
25 25
26 26 from rhodecode.lib import caches
27 27 from rhodecode.lib.utils2 import (
28 28 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
29 29 from rhodecode.lib.vcs.backends import base
30 30 from rhodecode.model import BaseModel
31 31 from rhodecode.model.db import (
32 32 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
33 33 from rhodecode.model.meta import Session
34 34
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 UiSetting = namedtuple(
40 40 'UiSetting', ['section', 'key', 'value', 'active'])
41 41
42 42 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
43 43
44 44
45 45 class SettingNotFound(Exception):
46 46 def __init__(self):
47 47 super(SettingNotFound, self).__init__('Setting is not found')
48 48
49 49
50 50 class SettingsModel(BaseModel):
51 51 BUILTIN_HOOKS = (
52 52 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
53 53 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
54 54 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL)
55 55 HOOKS_SECTION = 'hooks'
56 56
57 57 def __init__(self, sa=None, repo=None):
58 58 self.repo = repo
59 59 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
60 60 self.SettingsDbModel = (
61 61 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
62 62 super(SettingsModel, self).__init__(sa)
63 63
64 64 def get_ui_by_key(self, key):
65 65 q = self.UiDbModel.query()
66 66 q = q.filter(self.UiDbModel.ui_key == key)
67 67 q = self._filter_by_repo(RepoRhodeCodeUi, q)
68 68 return q.scalar()
69 69
70 70 def get_ui_by_section(self, section):
71 71 q = self.UiDbModel.query()
72 72 q = q.filter(self.UiDbModel.ui_section == section)
73 73 q = self._filter_by_repo(RepoRhodeCodeUi, q)
74 74 return q.all()
75 75
76 76 def get_ui_by_section_and_key(self, section, key):
77 77 q = self.UiDbModel.query()
78 78 q = q.filter(self.UiDbModel.ui_section == section)
79 79 q = q.filter(self.UiDbModel.ui_key == key)
80 80 q = self._filter_by_repo(RepoRhodeCodeUi, q)
81 81 return q.scalar()
82 82
83 83 def get_ui(self, section=None, key=None):
84 84 q = self.UiDbModel.query()
85 85 q = self._filter_by_repo(RepoRhodeCodeUi, q)
86 86
87 87 if section:
88 88 q = q.filter(self.UiDbModel.ui_section == section)
89 89 if key:
90 90 q = q.filter(self.UiDbModel.ui_key == key)
91 91
92 92 # TODO: mikhail: add caching
93 93 result = [
94 94 UiSetting(
95 95 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
96 96 value=safe_str(r.ui_value), active=r.ui_active
97 97 )
98 98 for r in q.all()
99 99 ]
100 100 return result
101 101
102 102 def get_builtin_hooks(self):
103 103 q = self.UiDbModel.query()
104 104 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
105 105 return self._get_hooks(q)
106 106
107 107 def get_custom_hooks(self):
108 108 q = self.UiDbModel.query()
109 109 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
110 110 return self._get_hooks(q)
111 111
112 112 def create_ui_section_value(self, section, val, key=None, active=True):
113 113 new_ui = self.UiDbModel()
114 114 new_ui.ui_section = section
115 115 new_ui.ui_value = val
116 116 new_ui.ui_active = active
117 117
118 118 if self.repo:
119 119 repo = self._get_repo(self.repo)
120 120 repository_id = repo.repo_id
121 121 new_ui.repository_id = repository_id
122 122
123 123 if not key:
124 124 # keys are unique so they need appended info
125 125 if self.repo:
126 126 key = hashlib.sha1(
127 127 '{}{}{}'.format(section, val, repository_id)).hexdigest()
128 128 else:
129 129 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
130 130
131 131 new_ui.ui_key = key
132 132
133 133 Session().add(new_ui)
134 134 return new_ui
135 135
136 136 def create_or_update_hook(self, key, value):
137 137 ui = (
138 138 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
139 139 self.UiDbModel())
140 140 ui.ui_section = self.HOOKS_SECTION
141 141 ui.ui_active = True
142 142 ui.ui_key = key
143 143 ui.ui_value = value
144 144
145 145 if self.repo:
146 146 repo = self._get_repo(self.repo)
147 147 repository_id = repo.repo_id
148 148 ui.repository_id = repository_id
149 149
150 150 Session().add(ui)
151 151 return ui
152 152
153 153 def delete_ui(self, id_):
154 154 ui = self.UiDbModel.get(id_)
155 155 if not ui:
156 156 raise SettingNotFound()
157 157 Session().delete(ui)
158 158
159 159 def get_setting_by_name(self, name):
160 160 q = self._get_settings_query()
161 161 q = q.filter(self.SettingsDbModel.app_settings_name == name)
162 162 return q.scalar()
163 163
164 164 def create_or_update_setting(
165 165 self, name, val=Optional(''), type_=Optional('unicode')):
166 166 """
167 167 Creates or updates RhodeCode setting. If updates is triggered it will
168 168 only update parameters that are explicityl set Optional instance will
169 169 be skipped
170 170
171 171 :param name:
172 172 :param val:
173 173 :param type_:
174 174 :return:
175 175 """
176 176
177 177 res = self.get_setting_by_name(name)
178 178 repo = self._get_repo(self.repo) if self.repo else None
179 179
180 180 if not res:
181 181 val = Optional.extract(val)
182 182 type_ = Optional.extract(type_)
183 183
184 184 args = (
185 185 (repo.repo_id, name, val, type_)
186 186 if repo else (name, val, type_))
187 187 res = self.SettingsDbModel(*args)
188 188
189 189 else:
190 190 if self.repo:
191 191 res.repository_id = repo.repo_id
192 192
193 193 res.app_settings_name = name
194 194 if not isinstance(type_, Optional):
195 195 # update if set
196 196 res.app_settings_type = type_
197 197 if not isinstance(val, Optional):
198 198 # update if set
199 199 res.app_settings_value = val
200 200
201 201 Session().add(res)
202 202 return res
203 203
204 204 def invalidate_settings_cache(self):
205 205 namespace = 'rhodecode_settings'
206 206 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
207 207 caches.clear_cache_manager(cache_manager)
208 208
209 209 def get_all_settings(self, cache=False):
210
210 211 def _compute():
211 212 q = self._get_settings_query()
212 213 if not q:
213 214 raise Exception('Could not get application settings !')
214 215
215 216 settings = {
216 217 'rhodecode_' + result.app_settings_name: result.app_settings_value
217 218 for result in q
218 219 }
219 220 return settings
220 221
221 222 if cache:
222 223 log.debug('Fetching app settings using cache')
223 224 repo = self._get_repo(self.repo) if self.repo else None
224 225 namespace = 'rhodecode_settings'
225 226 cache_manager = caches.get_cache_manager(
226 227 'sql_cache_short', namespace)
227 228 _cache_key = (
228 229 "get_repo_{}_settings".format(repo.repo_id)
229 230 if repo else "get_app_settings")
230 231
231 232 return cache_manager.get(_cache_key, createfunc=_compute)
232 233
233 234 else:
234 235 return _compute()
235 236
236 237 def get_auth_settings(self):
237 238 q = self._get_settings_query()
238 239 q = q.filter(
239 240 self.SettingsDbModel.app_settings_name.startswith('auth_'))
240 241 rows = q.all()
241 242 auth_settings = {
242 243 row.app_settings_name: row.app_settings_value for row in rows}
243 244 return auth_settings
244 245
245 246 def get_auth_plugins(self):
246 247 auth_plugins = self.get_setting_by_name("auth_plugins")
247 248 return auth_plugins.app_settings_value
248 249
249 250 def get_default_repo_settings(self, strip_prefix=False):
250 251 q = self._get_settings_query()
251 252 q = q.filter(
252 253 self.SettingsDbModel.app_settings_name.startswith('default_'))
253 254 rows = q.all()
254 255
255 256 result = {}
256 257 for row in rows:
257 258 key = row.app_settings_name
258 259 if strip_prefix:
259 260 key = remove_prefix(key, prefix='default_')
260 261 result.update({key: row.app_settings_value})
261 262 return result
262 263
263 264 def get_repo(self):
264 265 repo = self._get_repo(self.repo)
265 266 if not repo:
266 267 raise Exception(
267 268 'Repository `{}` cannot be found inside the database'.format(
268 269 self.repo))
269 270 return repo
270 271
271 272 def _filter_by_repo(self, model, query):
272 273 if self.repo:
273 274 repo = self.get_repo()
274 275 query = query.filter(model.repository_id == repo.repo_id)
275 276 return query
276 277
277 278 def _get_hooks(self, query):
278 279 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
279 280 query = self._filter_by_repo(RepoRhodeCodeUi, query)
280 281 return query.all()
281 282
282 283 def _get_settings_query(self):
283 284 q = self.SettingsDbModel.query()
284 285 return self._filter_by_repo(RepoRhodeCodeSetting, q)
285 286
286 287 def list_enabled_social_plugins(self, settings):
287 288 enabled = []
288 289 for plug in SOCIAL_PLUGINS_LIST:
289 290 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
290 291 )):
291 292 enabled.append(plug)
292 293 return enabled
293 294
294 295
295 296 def assert_repo_settings(func):
296 297 @wraps(func)
297 298 def _wrapper(self, *args, **kwargs):
298 299 if not self.repo_settings:
299 300 raise Exception('Repository is not specified')
300 301 return func(self, *args, **kwargs)
301 302 return _wrapper
302 303
303 304
304 305 class IssueTrackerSettingsModel(object):
305 306 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
306 307 SETTINGS_PREFIX = 'issuetracker_'
307 308
308 309 def __init__(self, sa=None, repo=None):
309 310 self.global_settings = SettingsModel(sa=sa)
310 311 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
311 312
312 313 @property
313 314 def inherit_global_settings(self):
314 315 if not self.repo_settings:
315 316 return True
316 317 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
317 318 return setting.app_settings_value if setting else True
318 319
319 320 @inherit_global_settings.setter
320 321 def inherit_global_settings(self, value):
321 322 if self.repo_settings:
322 323 settings = self.repo_settings.create_or_update_setting(
323 324 self.INHERIT_SETTINGS, value, type_='bool')
324 325 Session().add(settings)
325 326
326 327 def _get_keyname(self, key, uid, prefix=''):
327 328 return '{0}{1}{2}_{3}'.format(
328 329 prefix, self.SETTINGS_PREFIX, key, uid)
329 330
330 331 def _make_dict_for_settings(self, qs):
331 332 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
332 333
333 334 issuetracker_entries = {}
334 335 # create keys
335 336 for k, v in qs.items():
336 337 if k.startswith(prefix_match):
337 338 uid = k[len(prefix_match):]
338 339 issuetracker_entries[uid] = None
339 340
340 341 # populate
341 342 for uid in issuetracker_entries:
342 343 issuetracker_entries[uid] = AttributeDict({
343 344 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
344 345 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
345 346 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
346 347 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
347 348 })
348 349 return issuetracker_entries
349 350
350 351 def get_global_settings(self, cache=False):
351 352 """
352 353 Returns list of global issue tracker settings
353 354 """
354 355 defaults = self.global_settings.get_all_settings(cache=cache)
355 356 settings = self._make_dict_for_settings(defaults)
356 357 return settings
357 358
358 359 def get_repo_settings(self, cache=False):
359 360 """
360 361 Returns list of issue tracker settings per repository
361 362 """
362 363 if not self.repo_settings:
363 364 raise Exception('Repository is not specified')
364 365 all_settings = self.repo_settings.get_all_settings(cache=cache)
365 366 settings = self._make_dict_for_settings(all_settings)
366 367 return settings
367 368
368 369 def get_settings(self, cache=False):
369 370 if self.inherit_global_settings:
370 371 return self.get_global_settings(cache=cache)
371 372 else:
372 373 return self.get_repo_settings(cache=cache)
373 374
374 375 def delete_entries(self, uid):
375 376 if self.repo_settings:
376 377 all_patterns = self.get_repo_settings()
377 378 settings_model = self.repo_settings
378 379 else:
379 380 all_patterns = self.get_global_settings()
380 381 settings_model = self.global_settings
381 382 entries = all_patterns.get(uid)
382 383
383 384 for del_key in entries:
384 385 setting_name = self._get_keyname(del_key, uid)
385 386 entry = settings_model.get_setting_by_name(setting_name)
386 387 if entry:
387 388 Session().delete(entry)
388 389
389 390 Session().commit()
390 391
391 392 def create_or_update_setting(
392 393 self, name, val=Optional(''), type_=Optional('unicode')):
393 394 if self.repo_settings:
394 395 setting = self.repo_settings.create_or_update_setting(
395 396 name, val, type_)
396 397 else:
397 398 setting = self.global_settings.create_or_update_setting(
398 399 name, val, type_)
399 400 return setting
400 401
401 402
402 403 class VcsSettingsModel(object):
403 404
404 405 INHERIT_SETTINGS = 'inherit_vcs_settings'
405 406 GENERAL_SETTINGS = (
406 407 'use_outdated_comments',
407 408 'pr_merge_enabled',
408 409 'hg_use_rebase_for_merging')
409 410
410 411 HOOKS_SETTINGS = (
411 412 ('hooks', 'changegroup.repo_size'),
412 413 ('hooks', 'changegroup.push_logger'),
413 414 ('hooks', 'outgoing.pull_logger'),)
414 415 HG_SETTINGS = (
415 416 ('extensions', 'largefiles'),
416 417 ('phases', 'publish'),)
417 418 GIT_SETTINGS = (
418 419 ('vcs_git_lfs', 'enabled'),)
419 420
420 421 GLOBAL_HG_SETTINGS = (
421 422 ('extensions', 'largefiles'),
422 423 ('largefiles', 'usercache'),
423 424 ('phases', 'publish'),
424 425 ('extensions', 'hgsubversion'))
425 426 GLOBAL_GIT_SETTINGS = (
426 427 ('vcs_git_lfs', 'enabled'),
427 428 ('vcs_git_lfs', 'store_location'))
428 429 GLOBAL_SVN_SETTINGS = (
429 430 ('vcs_svn_proxy', 'http_requests_enabled'),
430 431 ('vcs_svn_proxy', 'http_server_url'))
431 432
432 433 SVN_BRANCH_SECTION = 'vcs_svn_branch'
433 434 SVN_TAG_SECTION = 'vcs_svn_tag'
434 435 SSL_SETTING = ('web', 'push_ssl')
435 436 PATH_SETTING = ('paths', '/')
436 437
437 438 def __init__(self, sa=None, repo=None):
438 439 self.global_settings = SettingsModel(sa=sa)
439 440 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
440 441 self._ui_settings = (
441 442 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
442 443 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
443 444
444 445 @property
445 446 @assert_repo_settings
446 447 def inherit_global_settings(self):
447 448 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
448 449 return setting.app_settings_value if setting else True
449 450
450 451 @inherit_global_settings.setter
451 452 @assert_repo_settings
452 453 def inherit_global_settings(self, value):
453 454 self.repo_settings.create_or_update_setting(
454 455 self.INHERIT_SETTINGS, value, type_='bool')
455 456
456 457 def get_global_svn_branch_patterns(self):
457 458 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
458 459
459 460 @assert_repo_settings
460 461 def get_repo_svn_branch_patterns(self):
461 462 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
462 463
463 464 def get_global_svn_tag_patterns(self):
464 465 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
465 466
466 467 @assert_repo_settings
467 468 def get_repo_svn_tag_patterns(self):
468 469 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
469 470
470 471 def get_global_settings(self):
471 472 return self._collect_all_settings(global_=True)
472 473
473 474 @assert_repo_settings
474 475 def get_repo_settings(self):
475 476 return self._collect_all_settings(global_=False)
476 477
477 478 @assert_repo_settings
478 479 def create_or_update_repo_settings(
479 480 self, data, inherit_global_settings=False):
480 481 from rhodecode.model.scm import ScmModel
481 482
482 483 self.inherit_global_settings = inherit_global_settings
483 484
484 485 repo = self.repo_settings.get_repo()
485 486 if not inherit_global_settings:
486 487 if repo.repo_type == 'svn':
487 488 self.create_repo_svn_settings(data)
488 489 else:
489 490 self.create_or_update_repo_hook_settings(data)
490 491 self.create_or_update_repo_pr_settings(data)
491 492
492 493 if repo.repo_type == 'hg':
493 494 self.create_or_update_repo_hg_settings(data)
494 495
495 496 if repo.repo_type == 'git':
496 497 self.create_or_update_repo_git_settings(data)
497 498
498 499 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
499 500
500 501 @assert_repo_settings
501 502 def create_or_update_repo_hook_settings(self, data):
502 503 for section, key in self.HOOKS_SETTINGS:
503 504 data_key = self._get_form_ui_key(section, key)
504 505 if data_key not in data:
505 506 raise ValueError(
506 507 'The given data does not contain {} key'.format(data_key))
507 508
508 509 active = data.get(data_key)
509 510 repo_setting = self.repo_settings.get_ui_by_section_and_key(
510 511 section, key)
511 512 if not repo_setting:
512 513 global_setting = self.global_settings.\
513 514 get_ui_by_section_and_key(section, key)
514 515 self.repo_settings.create_ui_section_value(
515 516 section, global_setting.ui_value, key=key, active=active)
516 517 else:
517 518 repo_setting.ui_active = active
518 519 Session().add(repo_setting)
519 520
520 521 def update_global_hook_settings(self, data):
521 522 for section, key in self.HOOKS_SETTINGS:
522 523 data_key = self._get_form_ui_key(section, key)
523 524 if data_key not in data:
524 525 raise ValueError(
525 526 'The given data does not contain {} key'.format(data_key))
526 527 active = data.get(data_key)
527 528 repo_setting = self.global_settings.get_ui_by_section_and_key(
528 529 section, key)
529 530 repo_setting.ui_active = active
530 531 Session().add(repo_setting)
531 532
532 533 @assert_repo_settings
533 534 def create_or_update_repo_pr_settings(self, data):
534 535 return self._create_or_update_general_settings(
535 536 self.repo_settings, data)
536 537
537 538 def create_or_update_global_pr_settings(self, data):
538 539 return self._create_or_update_general_settings(
539 540 self.global_settings, data)
540 541
541 542 @assert_repo_settings
542 543 def create_repo_svn_settings(self, data):
543 544 return self._create_svn_settings(self.repo_settings, data)
544 545
545 546 @assert_repo_settings
546 547 def create_or_update_repo_hg_settings(self, data):
547 548 largefiles, phases = \
548 549 self.HG_SETTINGS
549 550 largefiles_key, phases_key = \
550 551 self._get_settings_keys(self.HG_SETTINGS, data)
551 552
552 553 self._create_or_update_ui(
553 554 self.repo_settings, *largefiles, value='',
554 555 active=data[largefiles_key])
555 556 self._create_or_update_ui(
556 557 self.repo_settings, *phases, value=safe_str(data[phases_key]))
557 558
558 559 def create_or_update_global_hg_settings(self, data):
559 560 largefiles, largefiles_store, phases, hgsubversion \
560 561 = self.GLOBAL_HG_SETTINGS
561 562 largefiles_key, largefiles_store_key, phases_key, subversion_key \
562 563 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
563 564 self._create_or_update_ui(
564 565 self.global_settings, *largefiles, value='',
565 566 active=data[largefiles_key])
566 567 self._create_or_update_ui(
567 568 self.global_settings, *largefiles_store,
568 569 value=data[largefiles_store_key])
569 570 self._create_or_update_ui(
570 571 self.global_settings, *phases, value=safe_str(data[phases_key]))
571 572 self._create_or_update_ui(
572 573 self.global_settings, *hgsubversion, active=data[subversion_key])
573 574
574 575 def create_or_update_repo_git_settings(self, data):
575 576 # NOTE(marcink): # comma make unpack work properly
576 577 lfs_enabled, \
577 578 = self.GIT_SETTINGS
578 579
579 580 lfs_enabled_key, \
580 581 = self._get_settings_keys(self.GIT_SETTINGS, data)
581 582
582 583 self._create_or_update_ui(
583 584 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
584 585 active=data[lfs_enabled_key])
585 586
586 587 def create_or_update_global_git_settings(self, data):
587 588 lfs_enabled, lfs_store_location \
588 589 = self.GLOBAL_GIT_SETTINGS
589 590 lfs_enabled_key, lfs_store_location_key \
590 591 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
591 592
592 593 self._create_or_update_ui(
593 594 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
594 595 active=data[lfs_enabled_key])
595 596 self._create_or_update_ui(
596 597 self.global_settings, *lfs_store_location,
597 598 value=data[lfs_store_location_key])
598 599
599 600 def create_or_update_global_svn_settings(self, data):
600 601 # branch/tags patterns
601 602 self._create_svn_settings(self.global_settings, data)
602 603
603 604 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
604 605 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
605 606 self.GLOBAL_SVN_SETTINGS, data)
606 607
607 608 self._create_or_update_ui(
608 609 self.global_settings, *http_requests_enabled,
609 610 value=safe_str(data[http_requests_enabled_key]))
610 611 self._create_or_update_ui(
611 612 self.global_settings, *http_server_url,
612 613 value=data[http_server_url_key])
613 614
614 615 def update_global_ssl_setting(self, value):
615 616 self._create_or_update_ui(
616 617 self.global_settings, *self.SSL_SETTING, value=value)
617 618
618 619 def update_global_path_setting(self, value):
619 620 self._create_or_update_ui(
620 621 self.global_settings, *self.PATH_SETTING, value=value)
621 622
622 623 @assert_repo_settings
623 624 def delete_repo_svn_pattern(self, id_):
624 625 self.repo_settings.delete_ui(id_)
625 626
626 627 def delete_global_svn_pattern(self, id_):
627 628 self.global_settings.delete_ui(id_)
628 629
629 630 @assert_repo_settings
630 631 def get_repo_ui_settings(self, section=None, key=None):
631 632 global_uis = self.global_settings.get_ui(section, key)
632 633 repo_uis = self.repo_settings.get_ui(section, key)
633 634 filtered_repo_uis = self._filter_ui_settings(repo_uis)
634 635 filtered_repo_uis_keys = [
635 636 (s.section, s.key) for s in filtered_repo_uis]
636 637
637 638 def _is_global_ui_filtered(ui):
638 639 return (
639 640 (ui.section, ui.key) in filtered_repo_uis_keys
640 641 or ui.section in self._svn_sections)
641 642
642 643 filtered_global_uis = [
643 644 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
644 645
645 646 return filtered_global_uis + filtered_repo_uis
646 647
647 648 def get_global_ui_settings(self, section=None, key=None):
648 649 return self.global_settings.get_ui(section, key)
649 650
650 651 def get_ui_settings_as_config_obj(self, section=None, key=None):
651 652 config = base.Config()
652 653
653 654 ui_settings = self.get_ui_settings(section=section, key=key)
654 655
655 656 for entry in ui_settings:
656 657 config.set(entry.section, entry.key, entry.value)
657 658
658 659 return config
659 660
660 661 def get_ui_settings(self, section=None, key=None):
661 662 if not self.repo_settings or self.inherit_global_settings:
662 663 return self.get_global_ui_settings(section, key)
663 664 else:
664 665 return self.get_repo_ui_settings(section, key)
665 666
666 667 def get_svn_patterns(self, section=None):
667 668 if not self.repo_settings:
668 669 return self.get_global_ui_settings(section)
669 670 else:
670 671 return self.get_repo_ui_settings(section)
671 672
672 673 @assert_repo_settings
673 674 def get_repo_general_settings(self):
674 675 global_settings = self.global_settings.get_all_settings()
675 676 repo_settings = self.repo_settings.get_all_settings()
676 677 filtered_repo_settings = self._filter_general_settings(repo_settings)
677 678 global_settings.update(filtered_repo_settings)
678 679 return global_settings
679 680
680 681 def get_global_general_settings(self):
681 682 return self.global_settings.get_all_settings()
682 683
683 684 def get_general_settings(self):
684 685 if not self.repo_settings or self.inherit_global_settings:
685 686 return self.get_global_general_settings()
686 687 else:
687 688 return self.get_repo_general_settings()
688 689
689 690 def get_repos_location(self):
690 691 return self.global_settings.get_ui_by_key('/').ui_value
691 692
692 693 def _filter_ui_settings(self, settings):
693 694 filtered_settings = [
694 695 s for s in settings if self._should_keep_setting(s)]
695 696 return filtered_settings
696 697
697 698 def _should_keep_setting(self, setting):
698 699 keep = (
699 700 (setting.section, setting.key) in self._ui_settings or
700 701 setting.section in self._svn_sections)
701 702 return keep
702 703
703 704 def _filter_general_settings(self, settings):
704 705 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
705 706 return {
706 707 k: settings[k]
707 708 for k in settings if k in keys}
708 709
709 710 def _collect_all_settings(self, global_=False):
710 711 settings = self.global_settings if global_ else self.repo_settings
711 712 result = {}
712 713
713 714 for section, key in self._ui_settings:
714 715 ui = settings.get_ui_by_section_and_key(section, key)
715 716 result_key = self._get_form_ui_key(section, key)
716 717
717 718 if ui:
718 719 if section in ('hooks', 'extensions'):
719 720 result[result_key] = ui.ui_active
720 721 elif result_key in ['vcs_git_lfs_enabled']:
721 722 result[result_key] = ui.ui_active
722 723 else:
723 724 result[result_key] = ui.ui_value
724 725
725 726 for name in self.GENERAL_SETTINGS:
726 727 setting = settings.get_setting_by_name(name)
727 728 if setting:
728 729 result_key = 'rhodecode_{}'.format(name)
729 730 result[result_key] = setting.app_settings_value
730 731
731 732 return result
732 733
733 734 def _get_form_ui_key(self, section, key):
734 735 return '{section}_{key}'.format(
735 736 section=section, key=key.replace('.', '_'))
736 737
737 738 def _create_or_update_ui(
738 739 self, settings, section, key, value=None, active=None):
739 740 ui = settings.get_ui_by_section_and_key(section, key)
740 741 if not ui:
741 742 active = True if active is None else active
742 743 settings.create_ui_section_value(
743 744 section, value, key=key, active=active)
744 745 else:
745 746 if active is not None:
746 747 ui.ui_active = active
747 748 if value is not None:
748 749 ui.ui_value = value
749 750 Session().add(ui)
750 751
751 752 def _create_svn_settings(self, settings, data):
752 753 svn_settings = {
753 754 'new_svn_branch': self.SVN_BRANCH_SECTION,
754 755 'new_svn_tag': self.SVN_TAG_SECTION
755 756 }
756 757 for key in svn_settings:
757 758 if data.get(key):
758 759 settings.create_ui_section_value(svn_settings[key], data[key])
759 760
760 761 def _create_or_update_general_settings(self, settings, data):
761 762 for name in self.GENERAL_SETTINGS:
762 763 data_key = 'rhodecode_{}'.format(name)
763 764 if data_key not in data:
764 765 raise ValueError(
765 766 'The given data does not contain {} key'.format(data_key))
766 767 setting = settings.create_or_update_setting(
767 768 name, data[data_key], 'bool')
768 769 Session().add(setting)
769 770
770 771 def _get_settings_keys(self, settings, data):
771 772 data_keys = [self._get_form_ui_key(*s) for s in settings]
772 773 for data_key in data_keys:
773 774 if data_key not in data:
774 775 raise ValueError(
775 776 'The given data does not contain {} key'.format(data_key))
776 777 return data_keys
@@ -1,113 +1,114 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('home', '/', []);
16 16 pyroutes.register('new_repo', '/_admin/create_repository', []);
17 17 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
18 18 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
19 19 pyroutes.register('gists', '/_admin/gists', []);
20 20 pyroutes.register('new_gist', '/_admin/gists/new', []);
21 21 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
22 22 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
23 23 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
24 24 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
25 25 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
26 26 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
27 27 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
28 28 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
29 29 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
30 30 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
31 31 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
32 32 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
33 33 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
34 34 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
35 35 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
36 36 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
37 37 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
38 38 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
39 39 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
40 40 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
41 41 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
42 42 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
43 43 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
44 44 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 45 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
46 46 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 47 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 48 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
49 49 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
50 50 pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
51 51 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
52 52 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
53 53 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
54 54 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
55 55 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
56 56 pyroutes.register('favicon', '/favicon.ico', []);
57 57 pyroutes.register('robots', '/robots.txt', []);
58 58 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
59 59 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
60 60 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
61 61 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
62 62 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
63 63 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
64 64 pyroutes.register('repo_group_integrations_home', '%(repo_group_name)s/settings/integrations', ['repo_group_name']);
65 65 pyroutes.register('repo_group_integrations_list', '%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
66 66 pyroutes.register('repo_group_integrations_new', '%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
67 67 pyroutes.register('repo_group_integrations_create', '%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
68 68 pyroutes.register('repo_group_integrations_edit', '%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
69 69 pyroutes.register('repo_integrations_home', '%(repo_name)s/settings/integrations', ['repo_name']);
70 70 pyroutes.register('repo_integrations_list', '%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
71 71 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
72 72 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
73 73 pyroutes.register('repo_integrations_edit', '%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
74 pyroutes.register('ops_ping', '_admin/ops/ping', []);
74 75 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
75 76 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
76 77 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
77 78 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
78 79 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
79 80 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
80 81 pyroutes.register('users', '_admin/users', []);
81 82 pyroutes.register('users_data', '_admin/users_data', []);
82 83 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
83 84 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
84 85 pyroutes.register('edit_user_auth_tokens_delete', '_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
85 86 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
86 87 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
87 88 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
88 89 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
89 90 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
90 91 pyroutes.register('channelstream_proxy', '/_channelstream', []);
91 92 pyroutes.register('login', '/_admin/login', []);
92 93 pyroutes.register('logout', '/_admin/logout', []);
93 94 pyroutes.register('register', '/_admin/register', []);
94 95 pyroutes.register('reset_password', '/_admin/password_reset', []);
95 96 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
96 97 pyroutes.register('user_autocomplete_data', '/_users', []);
97 98 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
98 99 pyroutes.register('repo_list_data', '/_repos', []);
99 100 pyroutes.register('goto_switcher_data', '/_goto_data', []);
100 101 pyroutes.register('repo_maintenance', '/%(repo_name)s/maintenance', ['repo_name']);
101 102 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/maintenance/execute', ['repo_name']);
102 103 pyroutes.register('strip', '/%(repo_name)s/strip', ['repo_name']);
103 104 pyroutes.register('strip_check', '/%(repo_name)s/strip_check', ['repo_name']);
104 105 pyroutes.register('strip_execute', '/%(repo_name)s/strip_execute', ['repo_name']);
105 106 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
106 107 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
107 108 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
108 109 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
109 110 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
110 111 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
111 112 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
112 113 pyroutes.register('apiv2', '/_admin/api', []);
113 114 }
General Comments 0
You need to be logged in to leave comments. Login now