##// END OF EJS Templates
system-info: added UUID placeholder for generating platform unique identifiers.
marcink -
r1115:6e32b207 default
parent child Browse files
Show More
@@ -1,838 +1,839 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 settings controller for rhodecode admin
24 24 """
25 25
26 26 import collections
27 27 import logging
28 28 import urllib2
29 29
30 30 import datetime
31 31 import formencode
32 32 from formencode import htmlfill
33 33 import packaging.version
34 34 from pylons import request, tmpl_context as c, url, config
35 35 from pylons.controllers.util import redirect
36 36 from pylons.i18n.translation import _, lazy_ugettext
37 37 from pyramid.threadlocal import get_current_registry
38 38 from webob.exc import HTTPBadRequest
39 39
40 40 import rhodecode
41 41 from rhodecode.admin.navigation import navigation_list
42 42 from rhodecode.lib import auth
43 43 from rhodecode.lib import helpers as h
44 44 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
45 45 from rhodecode.lib.base import BaseController, render
46 46 from rhodecode.lib.celerylib import tasks, run_task
47 47 from rhodecode.lib.utils import repo2db_mapper
48 48 from rhodecode.lib.utils2 import (
49 49 str2bool, safe_unicode, AttributeDict, safe_int)
50 50 from rhodecode.lib.compat import OrderedDict
51 51 from rhodecode.lib.ext_json import json
52 52 from rhodecode.lib.utils import jsonify
53 53
54 54 from rhodecode.model.db import RhodeCodeUi, Repository
55 55 from rhodecode.model.forms import ApplicationSettingsForm, \
56 56 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
57 57 LabsSettingsForm, IssueTrackerPatternsForm
58 58 from rhodecode.model.repo_group import RepoGroupModel
59 59
60 60 from rhodecode.model.scm import ScmModel
61 61 from rhodecode.model.notification import EmailNotificationModel
62 62 from rhodecode.model.meta import Session
63 63 from rhodecode.model.settings import (
64 64 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
65 65 SettingsModel)
66 66
67 67 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
68 68 from rhodecode.svn_support.config_keys import generate_config
69 69
70 70
71 71 log = logging.getLogger(__name__)
72 72
73 73
74 74 class SettingsController(BaseController):
75 75 """REST Controller styled on the Atom Publishing Protocol"""
76 76 # To properly map this controller, ensure your config/routing.py
77 77 # file has a resource setup:
78 78 # map.resource('setting', 'settings', controller='admin/settings',
79 79 # path_prefix='/admin', name_prefix='admin_')
80 80
81 81 @LoginRequired()
82 82 def __before__(self):
83 83 super(SettingsController, self).__before__()
84 84 c.labs_active = str2bool(
85 85 rhodecode.CONFIG.get('labs_settings_active', 'true'))
86 86 c.navlist = navigation_list(request)
87 87
88 88 def _get_hg_ui_settings(self):
89 89 ret = RhodeCodeUi.query().all()
90 90
91 91 if not ret:
92 92 raise Exception('Could not get application ui settings !')
93 93 settings = {}
94 94 for each in ret:
95 95 k = each.ui_key
96 96 v = each.ui_value
97 97 if k == '/':
98 98 k = 'root_path'
99 99
100 100 if k in ['push_ssl', 'publish']:
101 101 v = str2bool(v)
102 102
103 103 if k.find('.') != -1:
104 104 k = k.replace('.', '_')
105 105
106 106 if each.ui_section in ['hooks', 'extensions']:
107 107 v = each.ui_active
108 108
109 109 settings[each.ui_section + '_' + k] = v
110 110 return settings
111 111
112 112 @HasPermissionAllDecorator('hg.admin')
113 113 @auth.CSRFRequired()
114 114 @jsonify
115 115 def delete_svn_pattern(self):
116 116 if not request.is_xhr:
117 117 raise HTTPBadRequest()
118 118
119 119 delete_pattern_id = request.POST.get('delete_svn_pattern')
120 120 model = VcsSettingsModel()
121 121 try:
122 122 model.delete_global_svn_pattern(delete_pattern_id)
123 123 except SettingNotFound:
124 124 raise HTTPBadRequest()
125 125
126 126 Session().commit()
127 127 return True
128 128
129 129 @HasPermissionAllDecorator('hg.admin')
130 130 @auth.CSRFRequired()
131 131 def settings_vcs_update(self):
132 132 """POST /admin/settings: All items in the collection"""
133 133 # url('admin_settings_vcs')
134 134 c.active = 'vcs'
135 135
136 136 model = VcsSettingsModel()
137 137 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
138 138 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
139 139
140 140 # TODO: Replace with request.registry after migrating to pyramid.
141 141 pyramid_settings = get_current_registry().settings
142 142 c.svn_proxy_generate_config = pyramid_settings[generate_config]
143 143
144 144 application_form = ApplicationUiSettingsForm()()
145 145
146 146 try:
147 147 form_result = application_form.to_python(dict(request.POST))
148 148 except formencode.Invalid as errors:
149 149 h.flash(
150 150 _("Some form inputs contain invalid data."),
151 151 category='error')
152 152 return htmlfill.render(
153 153 render('admin/settings/settings.html'),
154 154 defaults=errors.value,
155 155 errors=errors.error_dict or {},
156 156 prefix_error=False,
157 157 encoding="UTF-8",
158 158 force_defaults=False
159 159 )
160 160
161 161 try:
162 162 if c.visual.allow_repo_location_change:
163 163 model.update_global_path_setting(
164 164 form_result['paths_root_path'])
165 165
166 166 model.update_global_ssl_setting(form_result['web_push_ssl'])
167 167 model.update_global_hook_settings(form_result)
168 168
169 169 model.create_or_update_global_svn_settings(form_result)
170 170 model.create_or_update_global_hg_settings(form_result)
171 171 model.create_or_update_global_pr_settings(form_result)
172 172 except Exception:
173 173 log.exception("Exception while updating settings")
174 174 h.flash(_('Error occurred during updating '
175 175 'application settings'), category='error')
176 176 else:
177 177 Session().commit()
178 178 h.flash(_('Updated VCS settings'), category='success')
179 179 return redirect(url('admin_settings_vcs'))
180 180
181 181 return htmlfill.render(
182 182 render('admin/settings/settings.html'),
183 183 defaults=self._form_defaults(),
184 184 encoding="UTF-8",
185 185 force_defaults=False)
186 186
187 187 @HasPermissionAllDecorator('hg.admin')
188 188 def settings_vcs(self):
189 189 """GET /admin/settings: All items in the collection"""
190 190 # url('admin_settings_vcs')
191 191 c.active = 'vcs'
192 192 model = VcsSettingsModel()
193 193 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
194 194 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
195 195
196 196 # TODO: Replace with request.registry after migrating to pyramid.
197 197 pyramid_settings = get_current_registry().settings
198 198 c.svn_proxy_generate_config = pyramid_settings[generate_config]
199 199
200 200 return htmlfill.render(
201 201 render('admin/settings/settings.html'),
202 202 defaults=self._form_defaults(),
203 203 encoding="UTF-8",
204 204 force_defaults=False)
205 205
206 206 @HasPermissionAllDecorator('hg.admin')
207 207 @auth.CSRFRequired()
208 208 def settings_mapping_update(self):
209 209 """POST /admin/settings/mapping: All items in the collection"""
210 210 # url('admin_settings_mapping')
211 211 c.active = 'mapping'
212 212 rm_obsolete = request.POST.get('destroy', False)
213 213 invalidate_cache = request.POST.get('invalidate', False)
214 214 log.debug(
215 215 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
216 216
217 217 if invalidate_cache:
218 218 log.debug('invalidating all repositories cache')
219 219 for repo in Repository.get_all():
220 220 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
221 221
222 222 filesystem_repos = ScmModel().repo_scan()
223 223 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
224 224 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
225 225 h.flash(_('Repositories successfully '
226 226 'rescanned added: %s ; removed: %s') %
227 227 (_repr(added), _repr(removed)),
228 228 category='success')
229 229 return redirect(url('admin_settings_mapping'))
230 230
231 231 @HasPermissionAllDecorator('hg.admin')
232 232 def settings_mapping(self):
233 233 """GET /admin/settings/mapping: All items in the collection"""
234 234 # url('admin_settings_mapping')
235 235 c.active = 'mapping'
236 236
237 237 return htmlfill.render(
238 238 render('admin/settings/settings.html'),
239 239 defaults=self._form_defaults(),
240 240 encoding="UTF-8",
241 241 force_defaults=False)
242 242
243 243 @HasPermissionAllDecorator('hg.admin')
244 244 @auth.CSRFRequired()
245 245 def settings_global_update(self):
246 246 """POST /admin/settings/global: All items in the collection"""
247 247 # url('admin_settings_global')
248 248 c.active = 'global'
249 249 c.personal_repo_group_default_pattern = RepoGroupModel()\
250 250 .get_personal_group_name_pattern()
251 251 application_form = ApplicationSettingsForm()()
252 252 try:
253 253 form_result = application_form.to_python(dict(request.POST))
254 254 except formencode.Invalid as errors:
255 255 return htmlfill.render(
256 256 render('admin/settings/settings.html'),
257 257 defaults=errors.value,
258 258 errors=errors.error_dict or {},
259 259 prefix_error=False,
260 260 encoding="UTF-8",
261 261 force_defaults=False)
262 262
263 263 try:
264 264 settings = [
265 265 ('title', 'rhodecode_title', 'unicode'),
266 266 ('realm', 'rhodecode_realm', 'unicode'),
267 267 ('pre_code', 'rhodecode_pre_code', 'unicode'),
268 268 ('post_code', 'rhodecode_post_code', 'unicode'),
269 269 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
270 270 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
271 271 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
272 272 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
273 273 ]
274 274 for setting, form_key, type_ in settings:
275 275 sett = SettingsModel().create_or_update_setting(
276 276 setting, form_result[form_key], type_)
277 277 Session().add(sett)
278 278
279 279 Session().commit()
280 280 SettingsModel().invalidate_settings_cache()
281 281 h.flash(_('Updated application settings'), category='success')
282 282 except Exception:
283 283 log.exception("Exception while updating application settings")
284 284 h.flash(
285 285 _('Error occurred during updating application settings'),
286 286 category='error')
287 287
288 288 return redirect(url('admin_settings_global'))
289 289
290 290 @HasPermissionAllDecorator('hg.admin')
291 291 def settings_global(self):
292 292 """GET /admin/settings/global: All items in the collection"""
293 293 # url('admin_settings_global')
294 294 c.active = 'global'
295 295 c.personal_repo_group_default_pattern = RepoGroupModel()\
296 296 .get_personal_group_name_pattern()
297 297
298 298 return htmlfill.render(
299 299 render('admin/settings/settings.html'),
300 300 defaults=self._form_defaults(),
301 301 encoding="UTF-8",
302 302 force_defaults=False)
303 303
304 304 @HasPermissionAllDecorator('hg.admin')
305 305 @auth.CSRFRequired()
306 306 def settings_visual_update(self):
307 307 """POST /admin/settings/visual: All items in the collection"""
308 308 # url('admin_settings_visual')
309 309 c.active = 'visual'
310 310 application_form = ApplicationVisualisationForm()()
311 311 try:
312 312 form_result = application_form.to_python(dict(request.POST))
313 313 except formencode.Invalid as errors:
314 314 return htmlfill.render(
315 315 render('admin/settings/settings.html'),
316 316 defaults=errors.value,
317 317 errors=errors.error_dict or {},
318 318 prefix_error=False,
319 319 encoding="UTF-8",
320 320 force_defaults=False
321 321 )
322 322
323 323 try:
324 324 settings = [
325 325 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
326 326 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
327 327 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
328 328 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
329 329 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
330 330 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
331 331 ('show_version', 'rhodecode_show_version', 'bool'),
332 332 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
333 333 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
334 334 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
335 335 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
336 336 ('support_url', 'rhodecode_support_url', 'unicode'),
337 337 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
338 338 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
339 339 ]
340 340 for setting, form_key, type_ in settings:
341 341 sett = SettingsModel().create_or_update_setting(
342 342 setting, form_result[form_key], type_)
343 343 Session().add(sett)
344 344
345 345 Session().commit()
346 346 SettingsModel().invalidate_settings_cache()
347 347 h.flash(_('Updated visualisation settings'), category='success')
348 348 except Exception:
349 349 log.exception("Exception updating visualization settings")
350 350 h.flash(_('Error occurred during updating '
351 351 'visualisation settings'),
352 352 category='error')
353 353
354 354 return redirect(url('admin_settings_visual'))
355 355
356 356 @HasPermissionAllDecorator('hg.admin')
357 357 def settings_visual(self):
358 358 """GET /admin/settings/visual: All items in the collection"""
359 359 # url('admin_settings_visual')
360 360 c.active = 'visual'
361 361
362 362 return htmlfill.render(
363 363 render('admin/settings/settings.html'),
364 364 defaults=self._form_defaults(),
365 365 encoding="UTF-8",
366 366 force_defaults=False)
367 367
368 368 @HasPermissionAllDecorator('hg.admin')
369 369 @auth.CSRFRequired()
370 370 def settings_issuetracker_test(self):
371 371 if request.is_xhr:
372 372 return h.urlify_commit_message(
373 373 request.POST.get('test_text', ''),
374 374 'repo_group/test_repo1')
375 375 else:
376 376 raise HTTPBadRequest()
377 377
378 378 @HasPermissionAllDecorator('hg.admin')
379 379 @auth.CSRFRequired()
380 380 def settings_issuetracker_delete(self):
381 381 uid = request.POST.get('uid')
382 382 IssueTrackerSettingsModel().delete_entries(uid)
383 383 h.flash(_('Removed issue tracker entry'), category='success')
384 384 return redirect(url('admin_settings_issuetracker'))
385 385
386 386 @HasPermissionAllDecorator('hg.admin')
387 387 def settings_issuetracker(self):
388 388 """GET /admin/settings/issue-tracker: All items in the collection"""
389 389 # url('admin_settings_issuetracker')
390 390 c.active = 'issuetracker'
391 391 defaults = SettingsModel().get_all_settings()
392 392
393 393 entry_key = 'rhodecode_issuetracker_pat_'
394 394
395 395 c.issuetracker_entries = {}
396 396 for k, v in defaults.items():
397 397 if k.startswith(entry_key):
398 398 uid = k[len(entry_key):]
399 399 c.issuetracker_entries[uid] = None
400 400
401 401 for uid in c.issuetracker_entries:
402 402 c.issuetracker_entries[uid] = AttributeDict({
403 403 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
404 404 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
405 405 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
406 406 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
407 407 })
408 408
409 409 return render('admin/settings/settings.html')
410 410
411 411 @HasPermissionAllDecorator('hg.admin')
412 412 @auth.CSRFRequired()
413 413 def settings_issuetracker_save(self):
414 414 settings_model = IssueTrackerSettingsModel()
415 415
416 416 form = IssueTrackerPatternsForm()().to_python(request.POST)
417 417 if form:
418 418 for uid in form.get('delete_patterns', []):
419 419 settings_model.delete_entries(uid)
420 420
421 421 for pattern in form.get('patterns', []):
422 422 for setting, value, type_ in pattern:
423 423 sett = settings_model.create_or_update_setting(
424 424 setting, value, type_)
425 425 Session().add(sett)
426 426
427 427 Session().commit()
428 428
429 429 SettingsModel().invalidate_settings_cache()
430 430 h.flash(_('Updated issue tracker entries'), category='success')
431 431 return redirect(url('admin_settings_issuetracker'))
432 432
433 433 @HasPermissionAllDecorator('hg.admin')
434 434 @auth.CSRFRequired()
435 435 def settings_email_update(self):
436 436 """POST /admin/settings/email: All items in the collection"""
437 437 # url('admin_settings_email')
438 438 c.active = 'email'
439 439
440 440 test_email = request.POST.get('test_email')
441 441
442 442 if not test_email:
443 443 h.flash(_('Please enter email address'), category='error')
444 444 return redirect(url('admin_settings_email'))
445 445
446 446 email_kwargs = {
447 447 'date': datetime.datetime.now(),
448 448 'user': c.rhodecode_user,
449 449 'rhodecode_version': c.rhodecode_version
450 450 }
451 451
452 452 (subject, headers, email_body,
453 453 email_body_plaintext) = EmailNotificationModel().render_email(
454 454 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
455 455
456 456 recipients = [test_email] if test_email else None
457 457
458 458 run_task(tasks.send_email, recipients, subject,
459 459 email_body_plaintext, email_body)
460 460
461 461 h.flash(_('Send email task created'), category='success')
462 462 return redirect(url('admin_settings_email'))
463 463
464 464 @HasPermissionAllDecorator('hg.admin')
465 465 def settings_email(self):
466 466 """GET /admin/settings/email: All items in the collection"""
467 467 # url('admin_settings_email')
468 468 c.active = 'email'
469 469 c.rhodecode_ini = rhodecode.CONFIG
470 470
471 471 return htmlfill.render(
472 472 render('admin/settings/settings.html'),
473 473 defaults=self._form_defaults(),
474 474 encoding="UTF-8",
475 475 force_defaults=False)
476 476
477 477 @HasPermissionAllDecorator('hg.admin')
478 478 @auth.CSRFRequired()
479 479 def settings_hooks_update(self):
480 480 """POST or DELETE /admin/settings/hooks: All items in the collection"""
481 481 # url('admin_settings_hooks')
482 482 c.active = 'hooks'
483 483 if c.visual.allow_custom_hooks_settings:
484 484 ui_key = request.POST.get('new_hook_ui_key')
485 485 ui_value = request.POST.get('new_hook_ui_value')
486 486
487 487 hook_id = request.POST.get('hook_id')
488 488 new_hook = False
489 489
490 490 model = SettingsModel()
491 491 try:
492 492 if ui_value and ui_key:
493 493 model.create_or_update_hook(ui_key, ui_value)
494 494 h.flash(_('Added new hook'), category='success')
495 495 new_hook = True
496 496 elif hook_id:
497 497 RhodeCodeUi.delete(hook_id)
498 498 Session().commit()
499 499
500 500 # check for edits
501 501 update = False
502 502 _d = request.POST.dict_of_lists()
503 503 for k, v in zip(_d.get('hook_ui_key', []),
504 504 _d.get('hook_ui_value_new', [])):
505 505 model.create_or_update_hook(k, v)
506 506 update = True
507 507
508 508 if update and not new_hook:
509 509 h.flash(_('Updated hooks'), category='success')
510 510 Session().commit()
511 511 except Exception:
512 512 log.exception("Exception during hook creation")
513 513 h.flash(_('Error occurred during hook creation'),
514 514 category='error')
515 515
516 516 return redirect(url('admin_settings_hooks'))
517 517
518 518 @HasPermissionAllDecorator('hg.admin')
519 519 def settings_hooks(self):
520 520 """GET /admin/settings/hooks: All items in the collection"""
521 521 # url('admin_settings_hooks')
522 522 c.active = 'hooks'
523 523
524 524 model = SettingsModel()
525 525 c.hooks = model.get_builtin_hooks()
526 526 c.custom_hooks = model.get_custom_hooks()
527 527
528 528 return htmlfill.render(
529 529 render('admin/settings/settings.html'),
530 530 defaults=self._form_defaults(),
531 531 encoding="UTF-8",
532 532 force_defaults=False)
533 533
534 534 @HasPermissionAllDecorator('hg.admin')
535 535 def settings_search(self):
536 536 """GET /admin/settings/search: All items in the collection"""
537 537 # url('admin_settings_search')
538 538 c.active = 'search'
539 539
540 540 from rhodecode.lib.index import searcher_from_config
541 541 searcher = searcher_from_config(config)
542 542 c.statistics = searcher.statistics()
543 543
544 544 return render('admin/settings/settings.html')
545 545
546 546 @HasPermissionAllDecorator('hg.admin')
547 547 def settings_system(self):
548 548 """GET /admin/settings/system: All items in the collection"""
549 549 # url('admin_settings_system')
550 550 snapshot = str2bool(request.GET.get('snapshot'))
551 551 defaults = self._form_defaults()
552 552
553 553 c.active = 'system'
554 554 c.rhodecode_update_url = defaults.get('rhodecode_update_url')
555 555 server_info = ScmModel().get_server_info(request.environ)
556 556
557 557 for key, val in server_info.iteritems():
558 558 setattr(c, key, val)
559 559
560 560 def val(name, subkey='human_value'):
561 561 return server_info[name][subkey]
562 562
563 563 def state(name):
564 564 return server_info[name]['state']
565 565
566 566 def val2(name):
567 567 val = server_info[name]['human_value']
568 568 state = server_info[name]['state']
569 569 return val, state
570 570
571 571 c.data_items = [
572 572 # update info
573 573 (_('Update info'), h.literal(
574 574 '<span class="link" id="check_for_update" >%s.</span>' % (
575 575 _('Check for updates')) +
576 576 '<br/> <span >%s.</span>' % (_('Note: please make sure this server can access `%s` for the update link to work') % c.rhodecode_update_url)
577 577 ), ''),
578 578
579 579 # RhodeCode specific
580 580 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
581 581 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
582 582 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
583 583 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
584 584 ('', '', ''), # spacer
585 585
586 586 # Database
587 587 (_('Database'), val('database')['url'], state('database')),
588 588 (_('Database version'), val('database')['version'], state('database')),
589 589 ('', '', ''), # spacer
590 590
591 591 # Platform/Python
592 (_('Platform'), val('platform'), state('platform')),
592 (_('Platform'), val('platform')['name'], state('platform')),
593 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
593 594 (_('Python version'), val('python')['version'], state('python')),
594 595 (_('Python path'), val('python')['executable'], state('python')),
595 596 ('', '', ''), # spacer
596 597
597 598 # Systems stats
598 599 (_('CPU'), val('cpu'), state('cpu')),
599 600 (_('Load'), val('load')['text'], state('load')),
600 601 (_('Memory'), val('memory')['text'], state('memory')),
601 602 (_('Uptime'), val('uptime')['text'], state('uptime')),
602 603 ('', '', ''), # spacer
603 604
604 605 # Repo storage
605 606 (_('Storage location'), val('storage')['path'], state('storage')),
606 607 (_('Storage info'), val('storage')['text'], state('storage')),
607 608 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
608 609
609 610 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
610 611 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
611 612
612 613 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
613 614 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
614 615
615 616 (_('Search info'), val('search')['text'], state('search')),
616 617 (_('Search location'), val('search')['location'], state('search')),
617 618 ('', '', ''), # spacer
618 619
619 620 # VCS specific
620 621 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
621 622 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
622 623 (_('GIT'), val('git'), state('git')),
623 624 (_('HG'), val('hg'), state('hg')),
624 625 (_('SVN'), val('svn'), state('svn')),
625 626
626 627 ]
627 628
628 629 # TODO: marcink, figure out how to allow only selected users to do this
629 630 c.allowed_to_snapshot = c.rhodecode_user.admin
630 631
631 632 if snapshot:
632 633 if c.allowed_to_snapshot:
633 634 c.data_items.pop(0) # remove server info
634 635 return render('admin/settings/settings_system_snapshot.html')
635 636 else:
636 637 h.flash('You are not allowed to do this', category='warning')
637 638
638 639 return htmlfill.render(
639 640 render('admin/settings/settings.html'),
640 641 defaults=defaults,
641 642 encoding="UTF-8",
642 643 force_defaults=False)
643 644
644 645 @staticmethod
645 646 def get_update_data(update_url):
646 647 """Return the JSON update data."""
647 648 ver = rhodecode.__version__
648 649 log.debug('Checking for upgrade on `%s` server', update_url)
649 650 opener = urllib2.build_opener()
650 651 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
651 652 response = opener.open(update_url)
652 653 response_data = response.read()
653 654 data = json.loads(response_data)
654 655
655 656 return data
656 657
657 658 @HasPermissionAllDecorator('hg.admin')
658 659 def settings_system_update(self):
659 660 """GET /admin/settings/system/updates: All items in the collection"""
660 661 # url('admin_settings_system_update')
661 662 defaults = self._form_defaults()
662 663 update_url = defaults.get('rhodecode_update_url', '')
663 664
664 665 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
665 666 try:
666 667 data = self.get_update_data(update_url)
667 668 except urllib2.URLError as e:
668 669 log.exception("Exception contacting upgrade server")
669 670 return _err('Failed to contact upgrade server: %r' % e)
670 671 except ValueError as e:
671 672 log.exception("Bad data sent from update server")
672 673 return _err('Bad data sent from update server')
673 674
674 675 latest = data['versions'][0]
675 676
676 677 c.update_url = update_url
677 678 c.latest_data = latest
678 679 c.latest_ver = latest['version']
679 680 c.cur_ver = rhodecode.__version__
680 681 c.should_upgrade = False
681 682
682 683 if (packaging.version.Version(c.latest_ver) >
683 684 packaging.version.Version(c.cur_ver)):
684 685 c.should_upgrade = True
685 686 c.important_notices = latest['general']
686 687
687 688 return render('admin/settings/settings_system_update.html')
688 689
689 690 @HasPermissionAllDecorator('hg.admin')
690 691 def settings_supervisor(self):
691 692 c.rhodecode_ini = rhodecode.CONFIG
692 693 c.active = 'supervisor'
693 694
694 695 c.supervisor_procs = OrderedDict([
695 696 (SUPERVISOR_MASTER, {}),
696 697 ])
697 698
698 699 c.log_size = 10240
699 700 supervisor = SupervisorModel()
700 701
701 702 _connection = supervisor.get_connection(
702 703 c.rhodecode_ini.get('supervisor.uri'))
703 704 c.connection_error = None
704 705 try:
705 706 _connection.supervisor.getAllProcessInfo()
706 707 except Exception as e:
707 708 c.connection_error = str(e)
708 709 log.exception("Exception reading supervisor data")
709 710 return render('admin/settings/settings.html')
710 711
711 712 groupid = c.rhodecode_ini.get('supervisor.group_id')
712 713
713 714 # feed our group processes to the main
714 715 for proc in supervisor.get_group_processes(_connection, groupid):
715 716 c.supervisor_procs[proc['name']] = {}
716 717
717 718 for k in c.supervisor_procs.keys():
718 719 try:
719 720 # master process info
720 721 if k == SUPERVISOR_MASTER:
721 722 _data = supervisor.get_master_state(_connection)
722 723 _data['name'] = 'supervisor master'
723 724 _data['description'] = 'pid %s, id: %s, ver: %s' % (
724 725 _data['pid'], _data['id'], _data['ver'])
725 726 c.supervisor_procs[k] = _data
726 727 else:
727 728 procid = groupid + ":" + k
728 729 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
729 730 except Exception as e:
730 731 log.exception("Exception reading supervisor data")
731 732 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
732 733
733 734 return render('admin/settings/settings.html')
734 735
735 736 @HasPermissionAllDecorator('hg.admin')
736 737 def settings_supervisor_log(self, procid):
737 738 import rhodecode
738 739 c.rhodecode_ini = rhodecode.CONFIG
739 740 c.active = 'supervisor_tail'
740 741
741 742 supervisor = SupervisorModel()
742 743 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
743 744 groupid = c.rhodecode_ini.get('supervisor.group_id')
744 745 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
745 746
746 747 c.log_size = 10240
747 748 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
748 749 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
749 750
750 751 return render('admin/settings/settings.html')
751 752
752 753 @HasPermissionAllDecorator('hg.admin')
753 754 @auth.CSRFRequired()
754 755 def settings_labs_update(self):
755 756 """POST /admin/settings/labs: All items in the collection"""
756 757 # url('admin_settings/labs', method={'POST'})
757 758 c.active = 'labs'
758 759
759 760 application_form = LabsSettingsForm()()
760 761 try:
761 762 form_result = application_form.to_python(dict(request.POST))
762 763 except formencode.Invalid as errors:
763 764 h.flash(
764 765 _('Some form inputs contain invalid data.'),
765 766 category='error')
766 767 return htmlfill.render(
767 768 render('admin/settings/settings.html'),
768 769 defaults=errors.value,
769 770 errors=errors.error_dict or {},
770 771 prefix_error=False,
771 772 encoding='UTF-8',
772 773 force_defaults=False
773 774 )
774 775
775 776 try:
776 777 session = Session()
777 778 for setting in _LAB_SETTINGS:
778 779 setting_name = setting.key[len('rhodecode_'):]
779 780 sett = SettingsModel().create_or_update_setting(
780 781 setting_name, form_result[setting.key], setting.type)
781 782 session.add(sett)
782 783
783 784 except Exception:
784 785 log.exception('Exception while updating lab settings')
785 786 h.flash(_('Error occurred during updating labs settings'),
786 787 category='error')
787 788 else:
788 789 Session().commit()
789 790 SettingsModel().invalidate_settings_cache()
790 791 h.flash(_('Updated Labs settings'), category='success')
791 792 return redirect(url('admin_settings_labs'))
792 793
793 794 return htmlfill.render(
794 795 render('admin/settings/settings.html'),
795 796 defaults=self._form_defaults(),
796 797 encoding='UTF-8',
797 798 force_defaults=False)
798 799
799 800 @HasPermissionAllDecorator('hg.admin')
800 801 def settings_labs(self):
801 802 """GET /admin/settings/labs: All items in the collection"""
802 803 # url('admin_settings_labs')
803 804 if not c.labs_active:
804 805 redirect(url('admin_settings'))
805 806
806 807 c.active = 'labs'
807 808 c.lab_settings = _LAB_SETTINGS
808 809
809 810 return htmlfill.render(
810 811 render('admin/settings/settings.html'),
811 812 defaults=self._form_defaults(),
812 813 encoding='UTF-8',
813 814 force_defaults=False)
814 815
815 816 def _form_defaults(self):
816 817 defaults = SettingsModel().get_all_settings()
817 818 defaults.update(self._get_hg_ui_settings())
818 819 defaults.update({
819 820 'new_svn_branch': '',
820 821 'new_svn_tag': '',
821 822 })
822 823 return defaults
823 824
824 825
825 826 # :param key: name of the setting including the 'rhodecode_' prefix
826 827 # :param type: the RhodeCodeSetting type to use.
827 828 # :param group: the i18ned group in which we should dispaly this setting
828 829 # :param label: the i18ned label we should display for this setting
829 830 # :param help: the i18ned help we should dispaly for this setting
830 831 LabSetting = collections.namedtuple(
831 832 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
832 833
833 834
834 835 # This list has to be kept in sync with the form
835 836 # rhodecode.model.forms.LabsSettingsForm.
836 837 _LAB_SETTINGS = [
837 838
838 839 ]
@@ -1,607 +1,611 b''
1 1 import os
2 2 import sys
3 3 import time
4 4 import platform
5 5 import pkg_resources
6 6 import logging
7 7 import string
8 8
9 9
10 10 log = logging.getLogger(__name__)
11 11
12 12
13 13 psutil = None
14 14
15 15 try:
16 16 # cygwin cannot have yet psutil support.
17 17 import psutil as psutil
18 18 except ImportError:
19 19 pass
20 20
21 21
22 22 _NA = 'NOT AVAILABLE'
23 23
24 24 STATE_OK = 'ok'
25 25 STATE_ERR = 'error'
26 26 STATE_WARN = 'warning'
27 27
28 28 STATE_OK_DEFAULT = {'message': '', 'type': STATE_OK}
29 29
30 30
31 31 # HELPERS
32 32 def percentage(part, whole):
33 33 whole = float(whole)
34 34 if whole > 0:
35 35 return 100 * float(part) / whole
36 36 return 0
37 37
38 38
39 39 def get_storage_size(storage_path):
40 40 sizes = []
41 41 for file_ in os.listdir(storage_path):
42 42 storage_file = os.path.join(storage_path, file_)
43 43 if os.path.isfile(storage_file):
44 44 try:
45 45 sizes.append(os.path.getsize(storage_file))
46 46 except OSError:
47 47 log.exception('Failed to get size of storage file %s',
48 48 storage_file)
49 49 pass
50 50
51 51 return sum(sizes)
52 52
53 53
54 54 class SysInfoRes(object):
55 55 def __init__(self, value, state=STATE_OK_DEFAULT, human_value=None):
56 56 self.value = value
57 57 self.state = state
58 58 self.human_value = human_value or value
59 59
60 60 def __json__(self):
61 61 return {
62 62 'value': self.value,
63 63 'state': self.state,
64 64 'human_value': self.human_value,
65 65 }
66 66
67 67 def __str__(self):
68 68 return '<SysInfoRes({})>'.format(self.__json__())
69 69
70 70
71 71 class SysInfo(object):
72 72
73 73 def __init__(self, func_name, **kwargs):
74 74 self.func_name = func_name
75 75 self.value = _NA
76 76 self.state = None
77 77 self.kwargs = kwargs or {}
78 78
79 79 def __call__(self):
80 80 computed = self.compute(**self.kwargs)
81 81 if not isinstance(computed, SysInfoRes):
82 82 raise ValueError(
83 83 'computed value for {} is not instance of '
84 84 '{}, got {} instead'.format(
85 85 self.func_name, SysInfoRes, type(computed)))
86 86 return computed.__json__()
87 87
88 88 def __str__(self):
89 89 return '<SysInfo({})>'.format(self.func_name)
90 90
91 91 def compute(self, **kwargs):
92 92 return self.func_name(**kwargs)
93 93
94 94
95 95 # SysInfo functions
96 96 def python_info():
97 97 value = dict(version=' '.join(platform._sys_version()),
98 98 executable=sys.executable)
99 99 return SysInfoRes(value=value)
100 100
101 101
102 102 def py_modules():
103 103 mods = dict([(p.project_name, p.version)
104 104 for p in pkg_resources.working_set])
105 105 value = sorted(mods.items(), key=lambda k: k[0].lower())
106 106 return SysInfoRes(value=value)
107 107
108 108
109 109 def platform_type():
110 from rhodecode.lib.utils import safe_unicode
111 value = safe_unicode(platform.platform())
110 from rhodecode.lib.utils import safe_unicode, generate_platform_uuid
111
112 value = dict(
113 name=safe_unicode(platform.platform()),
114 uuid=generate_platform_uuid()
115 )
112 116 return SysInfoRes(value=value)
113 117
114 118
115 119 def uptime():
116 120 from rhodecode.lib.helpers import age, time_to_datetime
117 121
118 122 value = dict(boot_time=0, uptime=0, text='')
119 123 state = STATE_OK_DEFAULT
120 124 if not psutil:
121 125 return SysInfoRes(value=value, state=state)
122 126
123 127 boot_time = psutil.boot_time()
124 128 value['boot_time'] = boot_time
125 129 value['uptime'] = time.time() - boot_time
126 130
127 131 human_value = value.copy()
128 132 human_value['boot_time'] = time_to_datetime(boot_time)
129 133 human_value['uptime'] = age(time_to_datetime(boot_time), show_suffix=False)
130 134 human_value['text'] = 'Server started {}'.format(
131 135 age(time_to_datetime(boot_time)))
132 136
133 137 return SysInfoRes(value=value, human_value=human_value)
134 138
135 139
136 140 def memory():
137 141 from rhodecode.lib.helpers import format_byte_size_binary
138 142 value = dict(available=0, used=0, cached=0, percent=0, percent_used=0,
139 143 free=0, inactive=0, active=0, shared=0, total=0, buffers=0,
140 144 text='')
141 145
142 146 state = STATE_OK_DEFAULT
143 147 if not psutil:
144 148 return SysInfoRes(value=value, state=state)
145 149
146 150 value.update(dict(psutil.virtual_memory()._asdict()))
147 151 value['percent_used'] = psutil._common.usage_percent(
148 152 (value['total'] - value['free']), value['total'], 1)
149 153
150 154 human_value = value.copy()
151 155 human_value['text'] = '%s/%s, %s%% used' % (
152 156 format_byte_size_binary(value['used']),
153 157 format_byte_size_binary(value['total']),
154 158 value['percent_used'],)
155 159
156 160 keys = value.keys()[::]
157 161 keys.pop(keys.index('percent'))
158 162 keys.pop(keys.index('percent_used'))
159 163 keys.pop(keys.index('text'))
160 164 for k in keys:
161 165 human_value[k] = format_byte_size_binary(value[k])
162 166
163 167 if state['type'] == STATE_OK and value['percent_used'] > 90:
164 168 msg = 'Critical: your available RAM memory is very low.'
165 169 state = {'message': msg, 'type': STATE_ERR}
166 170
167 171 elif state['type'] == STATE_OK and value['percent_used'] > 70:
168 172 msg = 'Warning: your available RAM memory is running low.'
169 173 state = {'message': msg, 'type': STATE_WARN}
170 174
171 175 return SysInfoRes(value=value, state=state, human_value=human_value)
172 176
173 177
174 178 def machine_load():
175 179 value = {'1_min': _NA, '5_min': _NA, '15_min': _NA, 'text': ''}
176 180 state = STATE_OK_DEFAULT
177 181 if not psutil:
178 182 return SysInfoRes(value=value, state=state)
179 183
180 184 # load averages
181 185 if hasattr(psutil.os, 'getloadavg'):
182 186 value.update(dict(
183 187 zip(['1_min', '5_min', '15_min'], psutil.os.getloadavg())))
184 188
185 189 human_value = value.copy()
186 190 human_value['text'] = '1min: {}, 5min: {}, 15min: {}'.format(
187 191 value['1_min'], value['5_min'], value['15_min'])
188 192
189 193 if state['type'] == STATE_OK and value['15_min'] > 5:
190 194 msg = 'Warning: your machine load is very high.'
191 195 state = {'message': msg, 'type': STATE_WARN}
192 196
193 197 return SysInfoRes(value=value, state=state, human_value=human_value)
194 198
195 199
196 200 def cpu():
197 201 value = 0
198 202 state = STATE_OK_DEFAULT
199 203
200 204 if not psutil:
201 205 return SysInfoRes(value=value, state=state)
202 206
203 207 value = psutil.cpu_percent(0.5)
204 208 human_value = '{} %'.format(value)
205 209 return SysInfoRes(value=value, state=state, human_value=human_value)
206 210
207 211
208 212 def storage():
209 213 from rhodecode.lib.helpers import format_byte_size_binary
210 214 from rhodecode.model.settings import VcsSettingsModel
211 215 path = VcsSettingsModel().get_repos_location()
212 216
213 217 value = dict(percent=0, used=0, total=0, path=path, text='')
214 218 state = STATE_OK_DEFAULT
215 219 if not psutil:
216 220 return SysInfoRes(value=value, state=state)
217 221
218 222 try:
219 223 value.update(dict(psutil.disk_usage(path)._asdict()))
220 224 except Exception as e:
221 225 log.exception('Failed to fetch disk info')
222 226 state = {'message': str(e), 'type': STATE_ERR}
223 227
224 228 human_value = value.copy()
225 229 human_value['used'] = format_byte_size_binary(value['used'])
226 230 human_value['total'] = format_byte_size_binary(value['total'])
227 231 human_value['text'] = "{}/{}, {}% used".format(
228 232 format_byte_size_binary(value['used']),
229 233 format_byte_size_binary(value['total']),
230 234 value['percent'])
231 235
232 236 if state['type'] == STATE_OK and value['percent'] > 90:
233 237 msg = 'Critical: your disk space is very low.'
234 238 state = {'message': msg, 'type': STATE_ERR}
235 239
236 240 elif state['type'] == STATE_OK and value['percent'] > 70:
237 241 msg = 'Warning: your disk space is running low.'
238 242 state = {'message': msg, 'type': STATE_WARN}
239 243
240 244 return SysInfoRes(value=value, state=state, human_value=human_value)
241 245
242 246
243 247 def storage_inodes():
244 248 from rhodecode.model.settings import VcsSettingsModel
245 249 path = VcsSettingsModel().get_repos_location()
246 250
247 251 value = dict(percent=0, free=0, used=0, total=0, path=path, text='')
248 252 state = STATE_OK_DEFAULT
249 253 if not psutil:
250 254 return SysInfoRes(value=value, state=state)
251 255
252 256 try:
253 257 i_stat = os.statvfs(path)
254 258
255 259 value['used'] = i_stat.f_ffree
256 260 value['free'] = i_stat.f_favail
257 261 value['total'] = i_stat.f_files
258 262 value['percent'] = percentage(
259 263 value['used'], value['total'])
260 264 except Exception as e:
261 265 log.exception('Failed to fetch disk inodes info')
262 266 state = {'message': str(e), 'type': STATE_ERR}
263 267
264 268 human_value = value.copy()
265 269 human_value['text'] = "{}/{}, {}% used".format(
266 270 value['used'], value['total'], value['percent'])
267 271
268 272 if state['type'] == STATE_OK and value['percent'] > 90:
269 273 msg = 'Critical: your disk free inodes are very low.'
270 274 state = {'message': msg, 'type': STATE_ERR}
271 275
272 276 elif state['type'] == STATE_OK and value['percent'] > 70:
273 277 msg = 'Warning: your disk free inodes are running low.'
274 278 state = {'message': msg, 'type': STATE_WARN}
275 279
276 280 return SysInfoRes(value=value, state=state)
277 281
278 282
279 283 def storage_archives():
280 284 import rhodecode
281 285 from rhodecode.lib.utils import safe_str
282 286 from rhodecode.lib.helpers import format_byte_size_binary
283 287
284 288 msg = 'Enable this by setting ' \
285 289 'archive_cache_dir=/path/to/cache option in the .ini file'
286 290 path = safe_str(rhodecode.CONFIG.get('archive_cache_dir', msg))
287 291
288 292 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
289 293 state = STATE_OK_DEFAULT
290 294 try:
291 295 items_count = 0
292 296 used = 0
293 297 for root, dirs, files in os.walk(path):
294 298 if root == path:
295 299 items_count = len(files)
296 300
297 301 for f in files:
298 302 try:
299 303 used += os.path.getsize(os.path.join(root, f))
300 304 except OSError:
301 305 pass
302 306 value.update({
303 307 'percent': 100,
304 308 'used': used,
305 309 'total': used,
306 310 'items': items_count
307 311 })
308 312
309 313 except Exception as e:
310 314 log.exception('failed to fetch archive cache storage')
311 315 state = {'message': str(e), 'type': STATE_ERR}
312 316
313 317 human_value = value.copy()
314 318 human_value['used'] = format_byte_size_binary(value['used'])
315 319 human_value['total'] = format_byte_size_binary(value['total'])
316 320 human_value['text'] = "{} ({} items)".format(
317 321 human_value['used'], value['items'])
318 322
319 323 return SysInfoRes(value=value, state=state, human_value=human_value)
320 324
321 325
322 326 def storage_gist():
323 327 from rhodecode.model.gist import GIST_STORE_LOC
324 328 from rhodecode.model.settings import VcsSettingsModel
325 329 from rhodecode.lib.utils import safe_str
326 330 from rhodecode.lib.helpers import format_byte_size_binary
327 331 path = safe_str(os.path.join(
328 332 VcsSettingsModel().get_repos_location(), GIST_STORE_LOC))
329 333
330 334 # gist storage
331 335 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
332 336 state = STATE_OK_DEFAULT
333 337
334 338 try:
335 339 items_count = 0
336 340 used = 0
337 341 for root, dirs, files in os.walk(path):
338 342 if root == path:
339 343 items_count = len(dirs)
340 344
341 345 for f in files:
342 346 try:
343 347 used += os.path.getsize(os.path.join(root, f))
344 348 except OSError:
345 349 pass
346 350 value.update({
347 351 'percent': 100,
348 352 'used': used,
349 353 'total': used,
350 354 'items': items_count
351 355 })
352 356 except Exception as e:
353 357 log.exception('failed to fetch gist storage items')
354 358 state = {'message': str(e), 'type': STATE_ERR}
355 359
356 360 human_value = value.copy()
357 361 human_value['used'] = format_byte_size_binary(value['used'])
358 362 human_value['total'] = format_byte_size_binary(value['total'])
359 363 human_value['text'] = "{} ({} items)".format(
360 364 human_value['used'], value['items'])
361 365
362 366 return SysInfoRes(value=value, state=state, human_value=human_value)
363 367
364 368
365 369 def search_info():
366 370 import rhodecode
367 371 from rhodecode.lib.index import searcher_from_config
368 372
369 373 backend = rhodecode.CONFIG.get('search.module', '')
370 374 location = rhodecode.CONFIG.get('search.location', '')
371 375
372 376 try:
373 377 searcher = searcher_from_config(rhodecode.CONFIG)
374 378 searcher = searcher.__class__.__name__
375 379 except Exception:
376 380 searcher = None
377 381
378 382 value = dict(
379 383 backend=backend, searcher=searcher, location=location, text='')
380 384 state = STATE_OK_DEFAULT
381 385
382 386 human_value = value.copy()
383 387 human_value['text'] = "backend:`{}`".format(human_value['backend'])
384 388
385 389 return SysInfoRes(value=value, state=state, human_value=human_value)
386 390
387 391
388 392 def git_info():
389 393 from rhodecode.lib.vcs.backends import git
390 394 state = STATE_OK_DEFAULT
391 395 value = human_value = ''
392 396 try:
393 397 value = git.discover_git_version(raise_on_exc=True)
394 398 human_value = 'version reported from VCSServer: {}'.format(value)
395 399 except Exception as e:
396 400 state = {'message': str(e), 'type': STATE_ERR}
397 401
398 402 return SysInfoRes(value=value, state=state, human_value=human_value)
399 403
400 404
401 405 def hg_info():
402 406 from rhodecode.lib.vcs.backends import hg
403 407 state = STATE_OK_DEFAULT
404 408 value = human_value = ''
405 409 try:
406 410 value = hg.discover_hg_version(raise_on_exc=True)
407 411 human_value = 'version reported from VCSServer: {}'.format(value)
408 412 except Exception as e:
409 413 state = {'message': str(e), 'type': STATE_ERR}
410 414 return SysInfoRes(value=value, state=state, human_value=human_value)
411 415
412 416
413 417 def svn_info():
414 418 from rhodecode.lib.vcs.backends import svn
415 419 state = STATE_OK_DEFAULT
416 420 value = human_value = ''
417 421 try:
418 422 value = svn.discover_svn_version(raise_on_exc=True)
419 423 human_value = 'version reported from VCSServer: {}'.format(value)
420 424 except Exception as e:
421 425 state = {'message': str(e), 'type': STATE_ERR}
422 426 return SysInfoRes(value=value, state=state, human_value=human_value)
423 427
424 428
425 429 def vcs_backends():
426 430 import rhodecode
427 431 value = map(
428 432 string.strip, rhodecode.CONFIG.get('vcs.backends', '').split(','))
429 433 human_value = 'Enabled backends in order: {}'.format(','.join(value))
430 434 return SysInfoRes(value=value, human_value=human_value)
431 435
432 436
433 437 def vcs_server():
434 438 import rhodecode
435 439 from rhodecode.lib.vcs.backends import get_vcsserver_version
436 440
437 441 server_url = rhodecode.CONFIG.get('vcs.server')
438 442 enabled = rhodecode.CONFIG.get('vcs.server.enable')
439 443 protocol = rhodecode.CONFIG.get('vcs.server.protocol')
440 444 state = STATE_OK_DEFAULT
441 445 version = None
442 446
443 447 try:
444 448 version = get_vcsserver_version()
445 449 connection = 'connected'
446 450 except Exception as e:
447 451 connection = 'failed'
448 452 state = {'message': str(e), 'type': STATE_ERR}
449 453
450 454 value = dict(
451 455 url=server_url,
452 456 enabled=enabled,
453 457 protocol=protocol,
454 458 connection=connection,
455 459 version=version,
456 460 text='',
457 461 )
458 462
459 463 human_value = value.copy()
460 464 human_value['text'] = \
461 465 '{url}@ver:{ver} via {mode} mode, connection:{conn}'.format(
462 466 url=server_url, ver=version, mode=protocol, conn=connection)
463 467
464 468 return SysInfoRes(value=value, state=state, human_value=human_value)
465 469
466 470
467 471 def rhodecode_app_info():
468 472 import rhodecode
469 473 edition = rhodecode.CONFIG.get('rhodecode.edition')
470 474
471 475 value = dict(
472 476 rhodecode_version=rhodecode.__version__,
473 477 rhodecode_lib_path=os.path.abspath(rhodecode.__file__),
474 478 text=''
475 479 )
476 480 human_value = value.copy()
477 481 human_value['text'] = 'RhodeCode {edition}, version {ver}'.format(
478 482 edition=edition, ver=value['rhodecode_version']
479 483 )
480 484 return SysInfoRes(value=value, human_value=human_value)
481 485
482 486
483 487 def rhodecode_config():
484 488 import rhodecode
485 489 path = rhodecode.CONFIG.get('__file__')
486 490 rhodecode_ini_safe = rhodecode.CONFIG.copy()
487 491
488 492 blacklist = [
489 493 'rhodecode_license_key',
490 494 'routes.map',
491 495 'pylons.h',
492 496 'pylons.app_globals',
493 497 'pylons.environ_config',
494 498 'sqlalchemy.db1.url',
495 499 'channelstream.secret',
496 500 'beaker.session.secret',
497 501 'rhodecode.encrypted_values.secret',
498 502 'rhodecode_auth_github_consumer_key',
499 503 'rhodecode_auth_github_consumer_secret',
500 504 'rhodecode_auth_google_consumer_key',
501 505 'rhodecode_auth_google_consumer_secret',
502 506 'rhodecode_auth_bitbucket_consumer_secret',
503 507 'rhodecode_auth_bitbucket_consumer_key',
504 508 'rhodecode_auth_twitter_consumer_secret',
505 509 'rhodecode_auth_twitter_consumer_key',
506 510
507 511 'rhodecode_auth_twitter_secret',
508 512 'rhodecode_auth_github_secret',
509 513 'rhodecode_auth_google_secret',
510 514 'rhodecode_auth_bitbucket_secret',
511 515
512 516 'appenlight.api_key',
513 517 ('app_conf', 'sqlalchemy.db1.url')
514 518 ]
515 519 for k in blacklist:
516 520 if isinstance(k, tuple):
517 521 section, key = k
518 522 if section in rhodecode_ini_safe:
519 523 rhodecode_ini_safe[section] = '**OBFUSCATED**'
520 524 else:
521 525 rhodecode_ini_safe.pop(k, None)
522 526
523 527 # TODO: maybe put some CONFIG checks here ?
524 528 return SysInfoRes(value={'config': rhodecode_ini_safe, 'path': path})
525 529
526 530
527 531 def database_info():
528 532 import rhodecode
529 533 from sqlalchemy.engine import url as engine_url
530 534 from rhodecode.model.meta import Base as sql_base, Session
531 535 from rhodecode.model.db import DbMigrateVersion
532 536
533 537 state = STATE_OK_DEFAULT
534 538
535 539 db_migrate = DbMigrateVersion.query().filter(
536 540 DbMigrateVersion.repository_id == 'rhodecode_db_migrations').one()
537 541
538 542 db_url_obj = engine_url.make_url(rhodecode.CONFIG['sqlalchemy.db1.url'])
539 543
540 544 try:
541 545 engine = sql_base.metadata.bind
542 546 db_server_info = engine.dialect._get_server_version_info(
543 547 Session.connection(bind=engine))
544 548 db_version = '.'.join(map(str, db_server_info))
545 549 except Exception:
546 550 log.exception('failed to fetch db version')
547 551 db_version = 'UNKNOWN'
548 552
549 553 db_info = dict(
550 554 migrate_version=db_migrate.version,
551 555 type=db_url_obj.get_backend_name(),
552 556 version=db_version,
553 557 url=repr(db_url_obj)
554 558 )
555 559
556 560 human_value = db_info.copy()
557 561 human_value['url'] = "{} @ migration version: {}".format(
558 562 db_info['url'], db_info['migrate_version'])
559 563 human_value['version'] = "{} {}".format(db_info['type'], db_info['version'])
560 564 return SysInfoRes(value=db_info, state=state, human_value=human_value)
561 565
562 566
563 567 def server_info(environ):
564 568 import rhodecode
565 569 from rhodecode.lib.base import get_server_ip_addr, get_server_port
566 570
567 571 value = {
568 572 'server_ip': '%s:%s' % (
569 573 get_server_ip_addr(environ, log_errors=False),
570 574 get_server_port(environ)
571 575 ),
572 576 'server_id': rhodecode.CONFIG.get('instance_id'),
573 577 }
574 578 return SysInfoRes(value=value)
575 579
576 580
577 581 def get_system_info(environ):
578 582 environ = environ or {}
579 583 return {
580 584 'rhodecode_app': SysInfo(rhodecode_app_info)(),
581 585 'rhodecode_config': SysInfo(rhodecode_config)(),
582 586 'python': SysInfo(python_info)(),
583 587 'py_modules': SysInfo(py_modules)(),
584 588
585 589 'platform': SysInfo(platform_type)(),
586 590 'server': SysInfo(server_info, environ=environ)(),
587 591 'database': SysInfo(database_info)(),
588 592
589 593 'storage': SysInfo(storage)(),
590 594 'storage_inodes': SysInfo(storage_inodes)(),
591 595 'storage_archive': SysInfo(storage_archives)(),
592 596 'storage_gist': SysInfo(storage_gist)(),
593 597
594 598 'search': SysInfo(search_info)(),
595 599
596 600 'uptime': SysInfo(uptime)(),
597 601 'load': SysInfo(machine_load)(),
598 602 'cpu': SysInfo(cpu)(),
599 603 'memory': SysInfo(memory)(),
600 604
601 605 'vcs_backends': SysInfo(vcs_backends)(),
602 606 'vcs_server': SysInfo(vcs_server)(),
603 607
604 608 'git': SysInfo(git_info)(),
605 609 'hg': SysInfo(hg_info)(),
606 610 'svn': SysInfo(svn_info)(),
607 611 }
@@ -1,1003 +1,1018 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 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 import hashlib
36 37 from os.path import join as jn
37 38
38 39 import paste
39 40 import pkg_resources
40 41 from paste.script.command import Command, BadCommand
41 42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 43 from mako import exceptions
43 44 from pyramid.threadlocal import get_current_registry
44 45
45 46 from rhodecode.lib.fakemod import create_module
46 47 from rhodecode.lib.vcs.backends.base import Config
47 48 from rhodecode.lib.vcs.exceptions import VCSError
48 49 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
49 50 from rhodecode.lib.utils2 import (
50 51 safe_str, safe_unicode, get_current_rhodecode_user, md5)
51 52 from rhodecode.model import meta
52 53 from rhodecode.model.db import (
53 54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
54 55 from rhodecode.model.meta import Session
55 56
56 57
57 58 log = logging.getLogger(__name__)
58 59
59 60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
60 61
61 62 # String which contains characters that are not allowed in slug names for
62 63 # repositories or repository groups. It is properly escaped to use it in
63 64 # regular expressions.
64 65 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|: ')
65 66
66 67 # Regex that matches forbidden characters in repo/group slugs.
67 68 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
68 69
69 70 # Regex that matches allowed characters in repo/group slugs.
70 71 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
71 72
72 73 # Regex that matches whole repo/group slugs.
73 74 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
74 75
75 76 _license_cache = None
76 77
77 78
78 79 def repo_name_slug(value):
79 80 """
80 81 Return slug of name of repository
81 82 This function is called on each creation/modification
82 83 of repository to prevent bad names in repo
83 84 """
84 85 replacement_char = '-'
85 86
86 87 slug = remove_formatting(value)
87 88 slug = SLUG_BAD_CHAR_RE.sub(replacement_char, slug)
88 89 slug = collapse(slug, replacement_char)
89 90 return slug
90 91
91 92
92 93 #==============================================================================
93 94 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
94 95 #==============================================================================
95 96 def get_repo_slug(request):
96 97 _repo = request.environ['pylons.routes_dict'].get('repo_name')
97 98 if _repo:
98 99 _repo = _repo.rstrip('/')
99 100 return _repo
100 101
101 102
102 103 def get_repo_group_slug(request):
103 104 _group = request.environ['pylons.routes_dict'].get('group_name')
104 105 if _group:
105 106 _group = _group.rstrip('/')
106 107 return _group
107 108
108 109
109 110 def get_user_group_slug(request):
110 111 _group = request.environ['pylons.routes_dict'].get('user_group_id')
111 112 try:
112 113 _group = UserGroup.get(_group)
113 114 if _group:
114 115 _group = _group.users_group_name
115 116 except Exception:
116 117 log.debug(traceback.format_exc())
117 118 #catch all failures here
118 119 pass
119 120
120 121 return _group
121 122
122 123
123 124 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
124 125 """
125 126 Action logger for various actions made by users
126 127
127 128 :param user: user that made this action, can be a unique username string or
128 129 object containing user_id attribute
129 130 :param action: action to log, should be on of predefined unique actions for
130 131 easy translations
131 132 :param repo: string name of repository or object containing repo_id,
132 133 that action was made on
133 134 :param ipaddr: optional ip address from what the action was made
134 135 :param sa: optional sqlalchemy session
135 136
136 137 """
137 138
138 139 if not sa:
139 140 sa = meta.Session()
140 141 # if we don't get explicit IP address try to get one from registered user
141 142 # in tmpl context var
142 143 if not ipaddr:
143 144 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
144 145
145 146 try:
146 147 if getattr(user, 'user_id', None):
147 148 user_obj = User.get(user.user_id)
148 149 elif isinstance(user, basestring):
149 150 user_obj = User.get_by_username(user)
150 151 else:
151 152 raise Exception('You have to provide a user object or a username')
152 153
153 154 if getattr(repo, 'repo_id', None):
154 155 repo_obj = Repository.get(repo.repo_id)
155 156 repo_name = repo_obj.repo_name
156 157 elif isinstance(repo, basestring):
157 158 repo_name = repo.lstrip('/')
158 159 repo_obj = Repository.get_by_repo_name(repo_name)
159 160 else:
160 161 repo_obj = None
161 162 repo_name = ''
162 163
163 164 user_log = UserLog()
164 165 user_log.user_id = user_obj.user_id
165 166 user_log.username = user_obj.username
166 167 action = safe_unicode(action)
167 168 user_log.action = action[:1200000]
168 169
169 170 user_log.repository = repo_obj
170 171 user_log.repository_name = repo_name
171 172
172 173 user_log.action_date = datetime.datetime.now()
173 174 user_log.user_ip = ipaddr
174 175 sa.add(user_log)
175 176
176 177 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
177 178 action, safe_unicode(repo), user_obj, ipaddr)
178 179 if commit:
179 180 sa.commit()
180 181 except Exception:
181 182 log.error(traceback.format_exc())
182 183 raise
183 184
184 185
185 186 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
186 187 """
187 188 Scans given path for repos and return (name,(type,path)) tuple
188 189
189 190 :param path: path to scan for repositories
190 191 :param recursive: recursive search and return names with subdirs in front
191 192 """
192 193
193 194 # remove ending slash for better results
194 195 path = path.rstrip(os.sep)
195 196 log.debug('now scanning in %s location recursive:%s...', path, recursive)
196 197
197 198 def _get_repos(p):
198 199 dirpaths = _get_dirpaths(p)
199 200 if not _is_dir_writable(p):
200 201 log.warning('repo path without write access: %s', p)
201 202
202 203 for dirpath in dirpaths:
203 204 if os.path.isfile(os.path.join(p, dirpath)):
204 205 continue
205 206 cur_path = os.path.join(p, dirpath)
206 207
207 208 # skip removed repos
208 209 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
209 210 continue
210 211
211 212 #skip .<somethin> dirs
212 213 if dirpath.startswith('.'):
213 214 continue
214 215
215 216 try:
216 217 scm_info = get_scm(cur_path)
217 218 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
218 219 except VCSError:
219 220 if not recursive:
220 221 continue
221 222 #check if this dir containts other repos for recursive scan
222 223 rec_path = os.path.join(p, dirpath)
223 224 if os.path.isdir(rec_path):
224 225 for inner_scm in _get_repos(rec_path):
225 226 yield inner_scm
226 227
227 228 return _get_repos(path)
228 229
229 230
230 231 def _get_dirpaths(p):
231 232 try:
232 233 # OS-independable way of checking if we have at least read-only
233 234 # access or not.
234 235 dirpaths = os.listdir(p)
235 236 except OSError:
236 237 log.warning('ignoring repo path without read access: %s', p)
237 238 return []
238 239
239 240 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
240 241 # decode paths and suddenly returns unicode objects itself. The items it
241 242 # cannot decode are returned as strings and cause issues.
242 243 #
243 244 # Those paths are ignored here until a solid solution for path handling has
244 245 # been built.
245 246 expected_type = type(p)
246 247
247 248 def _has_correct_type(item):
248 249 if type(item) is not expected_type:
249 250 log.error(
250 251 u"Ignoring path %s since it cannot be decoded into unicode.",
251 252 # Using "repr" to make sure that we see the byte value in case
252 253 # of support.
253 254 repr(item))
254 255 return False
255 256 return True
256 257
257 258 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
258 259
259 260 return dirpaths
260 261
261 262
262 263 def _is_dir_writable(path):
263 264 """
264 265 Probe if `path` is writable.
265 266
266 267 Due to trouble on Cygwin / Windows, this is actually probing if it is
267 268 possible to create a file inside of `path`, stat does not produce reliable
268 269 results in this case.
269 270 """
270 271 try:
271 272 with tempfile.TemporaryFile(dir=path):
272 273 pass
273 274 except OSError:
274 275 return False
275 276 return True
276 277
277 278
278 279 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
279 280 """
280 281 Returns True if given path is a valid repository False otherwise.
281 282 If expect_scm param is given also, compare if given scm is the same
282 283 as expected from scm parameter. If explicit_scm is given don't try to
283 284 detect the scm, just use the given one to check if repo is valid
284 285
285 286 :param repo_name:
286 287 :param base_path:
287 288 :param expect_scm:
288 289 :param explicit_scm:
289 290
290 291 :return True: if given path is a valid repository
291 292 """
292 293 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
293 294 log.debug('Checking if `%s` is a valid path for repository', repo_name)
294 295
295 296 try:
296 297 if explicit_scm:
297 298 detected_scms = [get_scm_backend(explicit_scm)]
298 299 else:
299 300 detected_scms = get_scm(full_path)
300 301
301 302 if expect_scm:
302 303 return detected_scms[0] == expect_scm
303 304 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
304 305 return True
305 306 except VCSError:
306 307 log.debug('path: %s is not a valid repo !', full_path)
307 308 return False
308 309
309 310
310 311 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
311 312 """
312 313 Returns True if given path is a repository group, False otherwise
313 314
314 315 :param repo_name:
315 316 :param base_path:
316 317 """
317 318 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
318 319 log.debug('Checking if `%s` is a valid path for repository group',
319 320 repo_group_name)
320 321
321 322 # check if it's not a repo
322 323 if is_valid_repo(repo_group_name, base_path):
323 324 log.debug('Repo called %s exist, it is not a valid '
324 325 'repo group' % repo_group_name)
325 326 return False
326 327
327 328 try:
328 329 # we need to check bare git repos at higher level
329 330 # since we might match branches/hooks/info/objects or possible
330 331 # other things inside bare git repo
331 332 scm_ = get_scm(os.path.dirname(full_path))
332 333 log.debug('path: %s is a vcs object:%s, not valid '
333 334 'repo group' % (full_path, scm_))
334 335 return False
335 336 except VCSError:
336 337 pass
337 338
338 339 # check if it's a valid path
339 340 if skip_path_check or os.path.isdir(full_path):
340 341 log.debug('path: %s is a valid repo group !', full_path)
341 342 return True
342 343
343 344 log.debug('path: %s is not a valid repo group !', full_path)
344 345 return False
345 346
346 347
347 348 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
348 349 while True:
349 350 ok = raw_input(prompt)
350 351 if ok.lower() in ('y', 'ye', 'yes'):
351 352 return True
352 353 if ok.lower() in ('n', 'no', 'nop', 'nope'):
353 354 return False
354 355 retries = retries - 1
355 356 if retries < 0:
356 357 raise IOError
357 358 print(complaint)
358 359
359 360 # propagated from mercurial documentation
360 361 ui_sections = [
361 362 'alias', 'auth',
362 363 'decode/encode', 'defaults',
363 364 'diff', 'email',
364 365 'extensions', 'format',
365 366 'merge-patterns', 'merge-tools',
366 367 'hooks', 'http_proxy',
367 368 'smtp', 'patch',
368 369 'paths', 'profiling',
369 370 'server', 'trusted',
370 371 'ui', 'web', ]
371 372
372 373
373 374 def config_data_from_db(clear_session=True, repo=None):
374 375 """
375 376 Read the configuration data from the database and return configuration
376 377 tuples.
377 378 """
378 379 from rhodecode.model.settings import VcsSettingsModel
379 380
380 381 config = []
381 382
382 383 sa = meta.Session()
383 384 settings_model = VcsSettingsModel(repo=repo, sa=sa)
384 385
385 386 ui_settings = settings_model.get_ui_settings()
386 387
387 388 for setting in ui_settings:
388 389 if setting.active:
389 390 log.debug(
390 391 'settings ui from db: [%s] %s=%s',
391 392 setting.section, setting.key, setting.value)
392 393 config.append((
393 394 safe_str(setting.section), safe_str(setting.key),
394 395 safe_str(setting.value)))
395 396 if setting.key == 'push_ssl':
396 397 # force set push_ssl requirement to False, rhodecode
397 398 # handles that
398 399 config.append((
399 400 safe_str(setting.section), safe_str(setting.key), False))
400 401 if clear_session:
401 402 meta.Session.remove()
402 403
403 404 # TODO: mikhail: probably it makes no sense to re-read hooks information.
404 405 # It's already there and activated/deactivated
405 406 skip_entries = []
406 407 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
407 408 if 'pull' not in enabled_hook_classes:
408 409 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
409 410 if 'push' not in enabled_hook_classes:
410 411 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
411 412
412 413 config = [entry for entry in config if entry[:2] not in skip_entries]
413 414
414 415 return config
415 416
416 417
417 418 def make_db_config(clear_session=True, repo=None):
418 419 """
419 420 Create a :class:`Config` instance based on the values in the database.
420 421 """
421 422 config = Config()
422 423 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
423 424 for section, option, value in config_data:
424 425 config.set(section, option, value)
425 426 return config
426 427
427 428
428 429 def get_enabled_hook_classes(ui_settings):
429 430 """
430 431 Return the enabled hook classes.
431 432
432 433 :param ui_settings: List of ui_settings as returned
433 434 by :meth:`VcsSettingsModel.get_ui_settings`
434 435
435 436 :return: a list with the enabled hook classes. The order is not guaranteed.
436 437 :rtype: list
437 438 """
438 439 enabled_hooks = []
439 440 active_hook_keys = [
440 441 key for section, key, value, active in ui_settings
441 442 if section == 'hooks' and active]
442 443
443 444 hook_names = {
444 445 RhodeCodeUi.HOOK_PUSH: 'push',
445 446 RhodeCodeUi.HOOK_PULL: 'pull',
446 447 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
447 448 }
448 449
449 450 for key in active_hook_keys:
450 451 hook = hook_names.get(key)
451 452 if hook:
452 453 enabled_hooks.append(hook)
453 454
454 455 return enabled_hooks
455 456
456 457
457 458 def set_rhodecode_config(config):
458 459 """
459 460 Updates pylons config with new settings from database
460 461
461 462 :param config:
462 463 """
463 464 from rhodecode.model.settings import SettingsModel
464 465 app_settings = SettingsModel().get_all_settings()
465 466
466 467 for k, v in app_settings.items():
467 468 config[k] = v
468 469
469 470
470 471 def get_rhodecode_realm():
471 472 """
472 473 Return the rhodecode realm from database.
473 474 """
474 475 from rhodecode.model.settings import SettingsModel
475 476 realm = SettingsModel().get_setting_by_name('realm')
476 477 return safe_str(realm.app_settings_value)
477 478
478 479
479 480 def get_rhodecode_base_path():
480 481 """
481 482 Returns the base path. The base path is the filesystem path which points
482 483 to the repository store.
483 484 """
484 485 from rhodecode.model.settings import SettingsModel
485 486 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
486 487 return safe_str(paths_ui.ui_value)
487 488
488 489
489 490 def map_groups(path):
490 491 """
491 492 Given a full path to a repository, create all nested groups that this
492 493 repo is inside. This function creates parent-child relationships between
493 494 groups and creates default perms for all new groups.
494 495
495 496 :param paths: full path to repository
496 497 """
497 498 from rhodecode.model.repo_group import RepoGroupModel
498 499 sa = meta.Session()
499 500 groups = path.split(Repository.NAME_SEP)
500 501 parent = None
501 502 group = None
502 503
503 504 # last element is repo in nested groups structure
504 505 groups = groups[:-1]
505 506 rgm = RepoGroupModel(sa)
506 507 owner = User.get_first_super_admin()
507 508 for lvl, group_name in enumerate(groups):
508 509 group_name = '/'.join(groups[:lvl] + [group_name])
509 510 group = RepoGroup.get_by_group_name(group_name)
510 511 desc = '%s group' % group_name
511 512
512 513 # skip folders that are now removed repos
513 514 if REMOVED_REPO_PAT.match(group_name):
514 515 break
515 516
516 517 if group is None:
517 518 log.debug('creating group level: %s group_name: %s',
518 519 lvl, group_name)
519 520 group = RepoGroup(group_name, parent)
520 521 group.group_description = desc
521 522 group.user = owner
522 523 sa.add(group)
523 524 perm_obj = rgm._create_default_perms(group)
524 525 sa.add(perm_obj)
525 526 sa.flush()
526 527
527 528 parent = group
528 529 return group
529 530
530 531
531 532 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
532 533 """
533 534 maps all repos given in initial_repo_list, non existing repositories
534 535 are created, if remove_obsolete is True it also checks for db entries
535 536 that are not in initial_repo_list and removes them.
536 537
537 538 :param initial_repo_list: list of repositories found by scanning methods
538 539 :param remove_obsolete: check for obsolete entries in database
539 540 """
540 541 from rhodecode.model.repo import RepoModel
541 542 from rhodecode.model.scm import ScmModel
542 543 from rhodecode.model.repo_group import RepoGroupModel
543 544 from rhodecode.model.settings import SettingsModel
544 545
545 546 sa = meta.Session()
546 547 repo_model = RepoModel()
547 548 user = User.get_first_super_admin()
548 549 added = []
549 550
550 551 # creation defaults
551 552 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
552 553 enable_statistics = defs.get('repo_enable_statistics')
553 554 enable_locking = defs.get('repo_enable_locking')
554 555 enable_downloads = defs.get('repo_enable_downloads')
555 556 private = defs.get('repo_private')
556 557
557 558 for name, repo in initial_repo_list.items():
558 559 group = map_groups(name)
559 560 unicode_name = safe_unicode(name)
560 561 db_repo = repo_model.get_by_repo_name(unicode_name)
561 562 # found repo that is on filesystem not in RhodeCode database
562 563 if not db_repo:
563 564 log.info('repository %s not found, creating now', name)
564 565 added.append(name)
565 566 desc = (repo.description
566 567 if repo.description != 'unknown'
567 568 else '%s repository' % name)
568 569
569 570 db_repo = repo_model._create_repo(
570 571 repo_name=name,
571 572 repo_type=repo.alias,
572 573 description=desc,
573 574 repo_group=getattr(group, 'group_id', None),
574 575 owner=user,
575 576 enable_locking=enable_locking,
576 577 enable_downloads=enable_downloads,
577 578 enable_statistics=enable_statistics,
578 579 private=private,
579 580 state=Repository.STATE_CREATED
580 581 )
581 582 sa.commit()
582 583 # we added that repo just now, and make sure we updated server info
583 584 if db_repo.repo_type == 'git':
584 585 git_repo = db_repo.scm_instance()
585 586 # update repository server-info
586 587 log.debug('Running update server info')
587 588 git_repo._update_server_info()
588 589
589 590 db_repo.update_commit_cache()
590 591
591 592 config = db_repo._config
592 593 config.set('extensions', 'largefiles', '')
593 594 ScmModel().install_hooks(
594 595 db_repo.scm_instance(config=config),
595 596 repo_type=db_repo.repo_type)
596 597
597 598 removed = []
598 599 if remove_obsolete:
599 600 # remove from database those repositories that are not in the filesystem
600 601 for repo in sa.query(Repository).all():
601 602 if repo.repo_name not in initial_repo_list.keys():
602 603 log.debug("Removing non-existing repository found in db `%s`",
603 604 repo.repo_name)
604 605 try:
605 606 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
606 607 sa.commit()
607 608 removed.append(repo.repo_name)
608 609 except Exception:
609 610 # don't hold further removals on error
610 611 log.error(traceback.format_exc())
611 612 sa.rollback()
612 613
613 614 def splitter(full_repo_name):
614 615 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
615 616 gr_name = None
616 617 if len(_parts) == 2:
617 618 gr_name = _parts[0]
618 619 return gr_name
619 620
620 621 initial_repo_group_list = [splitter(x) for x in
621 622 initial_repo_list.keys() if splitter(x)]
622 623
623 624 # remove from database those repository groups that are not in the
624 625 # filesystem due to parent child relationships we need to delete them
625 626 # in a specific order of most nested first
626 627 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
627 628 nested_sort = lambda gr: len(gr.split('/'))
628 629 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
629 630 if group_name not in initial_repo_group_list:
630 631 repo_group = RepoGroup.get_by_group_name(group_name)
631 632 if (repo_group.children.all() or
632 633 not RepoGroupModel().check_exist_filesystem(
633 634 group_name=group_name, exc_on_failure=False)):
634 635 continue
635 636
636 637 log.info(
637 638 'Removing non-existing repository group found in db `%s`',
638 639 group_name)
639 640 try:
640 641 RepoGroupModel(sa).delete(group_name, fs_remove=False)
641 642 sa.commit()
642 643 removed.append(group_name)
643 644 except Exception:
644 645 # don't hold further removals on error
645 646 log.exception(
646 647 'Unable to remove repository group `%s`',
647 648 group_name)
648 649 sa.rollback()
649 650 raise
650 651
651 652 return added, removed
652 653
653 654
654 655 def get_default_cache_settings(settings):
655 656 cache_settings = {}
656 657 for key in settings.keys():
657 658 for prefix in ['beaker.cache.', 'cache.']:
658 659 if key.startswith(prefix):
659 660 name = key.split(prefix)[1].strip()
660 661 cache_settings[name] = settings[key].strip()
661 662 return cache_settings
662 663
663 664
664 665 # set cache regions for beaker so celery can utilise it
665 666 def add_cache(settings):
666 667 from rhodecode.lib import caches
667 668 cache_settings = {'regions': None}
668 669 # main cache settings used as default ...
669 670 cache_settings.update(get_default_cache_settings(settings))
670 671
671 672 if cache_settings['regions']:
672 673 for region in cache_settings['regions'].split(','):
673 674 region = region.strip()
674 675 region_settings = {}
675 676 for key, value in cache_settings.items():
676 677 if key.startswith(region):
677 678 region_settings[key.split('.')[1]] = value
678 679
679 680 caches.configure_cache_region(
680 681 region, region_settings, cache_settings)
681 682
682 683
683 684 def load_rcextensions(root_path):
684 685 import rhodecode
685 686 from rhodecode.config import conf
686 687
687 688 path = os.path.join(root_path, 'rcextensions', '__init__.py')
688 689 if os.path.isfile(path):
689 690 rcext = create_module('rc', path)
690 691 EXT = rhodecode.EXTENSIONS = rcext
691 692 log.debug('Found rcextensions now loading %s...', rcext)
692 693
693 694 # Additional mappings that are not present in the pygments lexers
694 695 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
695 696
696 697 # auto check if the module is not missing any data, set to default if is
697 698 # this will help autoupdate new feature of rcext module
698 699 #from rhodecode.config import rcextensions
699 700 #for k in dir(rcextensions):
700 701 # if not k.startswith('_') and not hasattr(EXT, k):
701 702 # setattr(EXT, k, getattr(rcextensions, k))
702 703
703 704
704 705 def get_custom_lexer(extension):
705 706 """
706 707 returns a custom lexer if it is defined in rcextensions module, or None
707 708 if there's no custom lexer defined
708 709 """
709 710 import rhodecode
710 711 from pygments import lexers
711 712 # check if we didn't define this extension as other lexer
712 713 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
713 714 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
714 715 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
715 716 return lexers.get_lexer_by_name(_lexer_name)
716 717
717 718
718 719 #==============================================================================
719 720 # TEST FUNCTIONS AND CREATORS
720 721 #==============================================================================
721 722 def create_test_index(repo_location, config):
722 723 """
723 724 Makes default test index.
724 725 """
725 726 import rc_testdata
726 727
727 728 rc_testdata.extract_search_index(
728 729 'vcs_search_index', os.path.dirname(config['search.location']))
729 730
730 731
731 732 def create_test_directory(test_path):
732 733 """
733 734 Create test directory if it doesn't exist.
734 735 """
735 736 if not os.path.isdir(test_path):
736 737 log.debug('Creating testdir %s', test_path)
737 738 os.makedirs(test_path)
738 739
739 740
740 741 def create_test_database(test_path, config):
741 742 """
742 743 Makes a fresh database.
743 744 """
744 745 from rhodecode.lib.db_manage import DbManage
745 746
746 747 # PART ONE create db
747 748 dbconf = config['sqlalchemy.db1.url']
748 749 log.debug('making test db %s', dbconf)
749 750
750 751 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
751 752 tests=True, cli_args={'force_ask': True})
752 753 dbmanage.create_tables(override=True)
753 754 dbmanage.set_db_version()
754 755 # for tests dynamically set new root paths based on generated content
755 756 dbmanage.create_settings(dbmanage.config_prompt(test_path))
756 757 dbmanage.create_default_user()
757 758 dbmanage.create_test_admin_and_users()
758 759 dbmanage.create_permissions()
759 760 dbmanage.populate_default_permissions()
760 761 Session().commit()
761 762
762 763
763 764 def create_test_repositories(test_path, config):
764 765 """
765 766 Creates test repositories in the temporary directory. Repositories are
766 767 extracted from archives within the rc_testdata package.
767 768 """
768 769 import rc_testdata
769 770 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
770 771
771 772 log.debug('making test vcs repositories')
772 773
773 774 idx_path = config['search.location']
774 775 data_path = config['cache_dir']
775 776
776 777 # clean index and data
777 778 if idx_path and os.path.exists(idx_path):
778 779 log.debug('remove %s', idx_path)
779 780 shutil.rmtree(idx_path)
780 781
781 782 if data_path and os.path.exists(data_path):
782 783 log.debug('remove %s', data_path)
783 784 shutil.rmtree(data_path)
784 785
785 786 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
786 787 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
787 788
788 789 # Note: Subversion is in the process of being integrated with the system,
789 790 # until we have a properly packed version of the test svn repository, this
790 791 # tries to copy over the repo from a package "rc_testdata"
791 792 svn_repo_path = rc_testdata.get_svn_repo_archive()
792 793 with tarfile.open(svn_repo_path) as tar:
793 794 tar.extractall(jn(test_path, SVN_REPO))
794 795
795 796
796 797 #==============================================================================
797 798 # PASTER COMMANDS
798 799 #==============================================================================
799 800 class BasePasterCommand(Command):
800 801 """
801 802 Abstract Base Class for paster commands.
802 803
803 804 The celery commands are somewhat aggressive about loading
804 805 celery.conf, and since our module sets the `CELERY_LOADER`
805 806 environment variable to our loader, we have to bootstrap a bit and
806 807 make sure we've had a chance to load the pylons config off of the
807 808 command line, otherwise everything fails.
808 809 """
809 810 min_args = 1
810 811 min_args_error = "Please provide a paster config file as an argument."
811 812 takes_config_file = 1
812 813 requires_config_file = True
813 814
814 815 def notify_msg(self, msg, log=False):
815 816 """Make a notification to user, additionally if logger is passed
816 817 it logs this action using given logger
817 818
818 819 :param msg: message that will be printed to user
819 820 :param log: logging instance, to use to additionally log this message
820 821
821 822 """
822 823 if log and isinstance(log, logging):
823 824 log(msg)
824 825
825 826 def run(self, args):
826 827 """
827 828 Overrides Command.run
828 829
829 830 Checks for a config file argument and loads it.
830 831 """
831 832 if len(args) < self.min_args:
832 833 raise BadCommand(
833 834 self.min_args_error % {'min_args': self.min_args,
834 835 'actual_args': len(args)})
835 836
836 837 # Decrement because we're going to lob off the first argument.
837 838 # @@ This is hacky
838 839 self.min_args -= 1
839 840 self.bootstrap_config(args[0])
840 841 self.update_parser()
841 842 return super(BasePasterCommand, self).run(args[1:])
842 843
843 844 def update_parser(self):
844 845 """
845 846 Abstract method. Allows for the class' parser to be updated
846 847 before the superclass' `run` method is called. Necessary to
847 848 allow options/arguments to be passed through to the underlying
848 849 celery command.
849 850 """
850 851 raise NotImplementedError("Abstract Method.")
851 852
852 853 def bootstrap_config(self, conf):
853 854 """
854 855 Loads the pylons configuration.
855 856 """
856 857 from pylons import config as pylonsconfig
857 858
858 859 self.path_to_ini_file = os.path.realpath(conf)
859 860 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
860 861 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
861 862
862 863 def _init_session(self):
863 864 """
864 865 Inits SqlAlchemy Session
865 866 """
866 867 logging.config.fileConfig(self.path_to_ini_file)
867 868 from pylons import config
868 869 from rhodecode.config.utils import initialize_database
869 870
870 871 # get to remove repos !!
871 872 add_cache(config)
872 873 initialize_database(config)
873 874
874 875
875 876 @decorator.decorator
876 877 def jsonify(func, *args, **kwargs):
877 878 """Action decorator that formats output for JSON
878 879
879 880 Given a function that will return content, this decorator will turn
880 881 the result into JSON, with a content-type of 'application/json' and
881 882 output it.
882 883
883 884 """
884 885 from pylons.decorators.util import get_pylons
885 886 from rhodecode.lib.ext_json import json
886 887 pylons = get_pylons(args)
887 888 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
888 889 data = func(*args, **kwargs)
889 890 if isinstance(data, (list, tuple)):
890 891 msg = "JSON responses with Array envelopes are susceptible to " \
891 892 "cross-site data leak attacks, see " \
892 893 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
893 894 warnings.warn(msg, Warning, 2)
894 895 log.warning(msg)
895 896 log.debug("Returning JSON wrapped action output")
896 897 return json.dumps(data, encoding='utf-8')
897 898
898 899
899 900 class PartialRenderer(object):
900 901 """
901 902 Partial renderer used to render chunks of html used in datagrids
902 903 use like::
903 904
904 905 _render = PartialRenderer('data_table/_dt_elements.html')
905 906 _render('quick_menu', args, kwargs)
906 907 PartialRenderer.h,
907 908 c,
908 909 _,
909 910 ungettext
910 911 are the template stuff initialized inside and can be re-used later
911 912
912 913 :param tmpl_name: template path relate to /templates/ dir
913 914 """
914 915
915 916 def __init__(self, tmpl_name):
916 917 import rhodecode
917 918 from pylons import request, tmpl_context as c
918 919 from pylons.i18n.translation import _, ungettext
919 920 from rhodecode.lib import helpers as h
920 921
921 922 self.tmpl_name = tmpl_name
922 923 self.rhodecode = rhodecode
923 924 self.c = c
924 925 self._ = _
925 926 self.ungettext = ungettext
926 927 self.h = h
927 928 self.request = request
928 929
929 930 def _mako_lookup(self):
930 931 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
931 932 return _tmpl_lookup.get_template(self.tmpl_name)
932 933
933 934 def _update_kwargs_for_render(self, kwargs):
934 935 """
935 936 Inject params required for Mako rendering
936 937 """
937 938 _kwargs = {
938 939 '_': self._,
939 940 'h': self.h,
940 941 'c': self.c,
941 942 'request': self.request,
942 943 'ungettext': self.ungettext,
943 944 }
944 945 _kwargs.update(kwargs)
945 946 return _kwargs
946 947
947 948 def _render_with_exc(self, render_func, args, kwargs):
948 949 try:
949 950 return render_func.render(*args, **kwargs)
950 951 except:
951 952 log.error(exceptions.text_error_template().render())
952 953 raise
953 954
954 955 def _get_template(self, template_obj, def_name):
955 956 if def_name:
956 957 tmpl = template_obj.get_def(def_name)
957 958 else:
958 959 tmpl = template_obj
959 960 return tmpl
960 961
961 962 def render(self, def_name, *args, **kwargs):
962 963 lookup_obj = self._mako_lookup()
963 964 tmpl = self._get_template(lookup_obj, def_name=def_name)
964 965 kwargs = self._update_kwargs_for_render(kwargs)
965 966 return self._render_with_exc(tmpl, args, kwargs)
966 967
967 968 def __call__(self, tmpl, *args, **kwargs):
968 969 return self.render(tmpl, *args, **kwargs)
969 970
970 971
971 972 def password_changed(auth_user, session):
972 973 # Never report password change in case of default user or anonymous user.
973 974 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
974 975 return False
975 976
976 977 password_hash = md5(auth_user.password) if auth_user.password else None
977 978 rhodecode_user = session.get('rhodecode_user', {})
978 979 session_password_hash = rhodecode_user.get('password', '')
979 980 return password_hash != session_password_hash
980 981
981 982
982 983 def read_opensource_licenses():
983 984 global _license_cache
984 985
985 986 if not _license_cache:
986 987 licenses = pkg_resources.resource_string(
987 988 'rhodecode', 'config/licenses.json')
988 989 _license_cache = json.loads(licenses)
989 990
990 991 return _license_cache
991 992
992 993
993 994 def get_registry(request):
994 995 """
995 996 Utility to get the pyramid registry from a request. During migration to
996 997 pyramid we sometimes want to use the pyramid registry from pylons context.
997 998 Therefore this utility returns `request.registry` for pyramid requests and
998 999 uses `get_current_registry()` for pylons requests.
999 1000 """
1000 1001 try:
1001 1002 return request.registry
1002 1003 except AttributeError:
1003 1004 return get_current_registry()
1005
1006
1007 def generate_platform_uuid():
1008 """
1009 Generates platform UUID based on it's name
1010 """
1011 import platform
1012
1013 try:
1014 uuid_list = [platform.platform()]
1015 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
1016 except Exception as e:
1017 log.error('Failed to generate host uuid: %s' % e)
1018 return 'UNDEFINED'
General Comments 0
You need to be logged in to leave comments. Login now