##// END OF EJS Templates
slugs: make special chars not be replaced by '-'. THis produces a much...
marcink -
r1147:0b331b7a default
parent child Browse files
Show More
@@ -1,1018 +1,1019 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 36 import hashlib
37 37 from os.path import join as jn
38 38
39 39 import paste
40 40 import pkg_resources
41 41 from paste.script.command import Command, BadCommand
42 42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 43 from mako import exceptions
44 44 from pyramid.threadlocal import get_current_registry
45 45
46 46 from rhodecode.lib.fakemod import create_module
47 47 from rhodecode.lib.vcs.backends.base import Config
48 48 from rhodecode.lib.vcs.exceptions import VCSError
49 49 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 50 from rhodecode.lib.utils2 import (
51 51 safe_str, safe_unicode, get_current_rhodecode_user, md5)
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import (
54 54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 55 from rhodecode.model.meta import Session
56 56
57 57
58 58 log = logging.getLogger(__name__)
59 59
60 60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 61
62 62 # String which contains characters that are not allowed in slug names for
63 63 # repositories or repository groups. It is properly escaped to use it in
64 64 # regular expressions.
65 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|: ')
65 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 66
67 67 # Regex that matches forbidden characters in repo/group slugs.
68 68 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69 69
70 70 # Regex that matches allowed characters in repo/group slugs.
71 71 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72 72
73 73 # Regex that matches whole repo/group slugs.
74 74 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75 75
76 76 _license_cache = None
77 77
78 78
79 79 def repo_name_slug(value):
80 80 """
81 81 Return slug of name of repository
82 82 This function is called on each creation/modification
83 83 of repository to prevent bad names in repo
84 84 """
85 85 replacement_char = '-'
86 86
87 87 slug = remove_formatting(value)
88 slug = SLUG_BAD_CHAR_RE.sub(replacement_char, slug)
88 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 slug = re.sub('[\s]+', '-', slug)
89 90 slug = collapse(slug, replacement_char)
90 91 return slug
91 92
92 93
93 94 #==============================================================================
94 95 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
95 96 #==============================================================================
96 97 def get_repo_slug(request):
97 98 _repo = request.environ['pylons.routes_dict'].get('repo_name')
98 99 if _repo:
99 100 _repo = _repo.rstrip('/')
100 101 return _repo
101 102
102 103
103 104 def get_repo_group_slug(request):
104 105 _group = request.environ['pylons.routes_dict'].get('group_name')
105 106 if _group:
106 107 _group = _group.rstrip('/')
107 108 return _group
108 109
109 110
110 111 def get_user_group_slug(request):
111 112 _group = request.environ['pylons.routes_dict'].get('user_group_id')
112 113 try:
113 114 _group = UserGroup.get(_group)
114 115 if _group:
115 116 _group = _group.users_group_name
116 117 except Exception:
117 118 log.debug(traceback.format_exc())
118 119 #catch all failures here
119 120 pass
120 121
121 122 return _group
122 123
123 124
124 125 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
125 126 """
126 127 Action logger for various actions made by users
127 128
128 129 :param user: user that made this action, can be a unique username string or
129 130 object containing user_id attribute
130 131 :param action: action to log, should be on of predefined unique actions for
131 132 easy translations
132 133 :param repo: string name of repository or object containing repo_id,
133 134 that action was made on
134 135 :param ipaddr: optional ip address from what the action was made
135 136 :param sa: optional sqlalchemy session
136 137
137 138 """
138 139
139 140 if not sa:
140 141 sa = meta.Session()
141 142 # if we don't get explicit IP address try to get one from registered user
142 143 # in tmpl context var
143 144 if not ipaddr:
144 145 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
145 146
146 147 try:
147 148 if getattr(user, 'user_id', None):
148 149 user_obj = User.get(user.user_id)
149 150 elif isinstance(user, basestring):
150 151 user_obj = User.get_by_username(user)
151 152 else:
152 153 raise Exception('You have to provide a user object or a username')
153 154
154 155 if getattr(repo, 'repo_id', None):
155 156 repo_obj = Repository.get(repo.repo_id)
156 157 repo_name = repo_obj.repo_name
157 158 elif isinstance(repo, basestring):
158 159 repo_name = repo.lstrip('/')
159 160 repo_obj = Repository.get_by_repo_name(repo_name)
160 161 else:
161 162 repo_obj = None
162 163 repo_name = ''
163 164
164 165 user_log = UserLog()
165 166 user_log.user_id = user_obj.user_id
166 167 user_log.username = user_obj.username
167 168 action = safe_unicode(action)
168 169 user_log.action = action[:1200000]
169 170
170 171 user_log.repository = repo_obj
171 172 user_log.repository_name = repo_name
172 173
173 174 user_log.action_date = datetime.datetime.now()
174 175 user_log.user_ip = ipaddr
175 176 sa.add(user_log)
176 177
177 178 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
178 179 action, safe_unicode(repo), user_obj, ipaddr)
179 180 if commit:
180 181 sa.commit()
181 182 except Exception:
182 183 log.error(traceback.format_exc())
183 184 raise
184 185
185 186
186 187 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
187 188 """
188 189 Scans given path for repos and return (name,(type,path)) tuple
189 190
190 191 :param path: path to scan for repositories
191 192 :param recursive: recursive search and return names with subdirs in front
192 193 """
193 194
194 195 # remove ending slash for better results
195 196 path = path.rstrip(os.sep)
196 197 log.debug('now scanning in %s location recursive:%s...', path, recursive)
197 198
198 199 def _get_repos(p):
199 200 dirpaths = _get_dirpaths(p)
200 201 if not _is_dir_writable(p):
201 202 log.warning('repo path without write access: %s', p)
202 203
203 204 for dirpath in dirpaths:
204 205 if os.path.isfile(os.path.join(p, dirpath)):
205 206 continue
206 207 cur_path = os.path.join(p, dirpath)
207 208
208 209 # skip removed repos
209 210 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
210 211 continue
211 212
212 213 #skip .<somethin> dirs
213 214 if dirpath.startswith('.'):
214 215 continue
215 216
216 217 try:
217 218 scm_info = get_scm(cur_path)
218 219 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
219 220 except VCSError:
220 221 if not recursive:
221 222 continue
222 223 #check if this dir containts other repos for recursive scan
223 224 rec_path = os.path.join(p, dirpath)
224 225 if os.path.isdir(rec_path):
225 226 for inner_scm in _get_repos(rec_path):
226 227 yield inner_scm
227 228
228 229 return _get_repos(path)
229 230
230 231
231 232 def _get_dirpaths(p):
232 233 try:
233 234 # OS-independable way of checking if we have at least read-only
234 235 # access or not.
235 236 dirpaths = os.listdir(p)
236 237 except OSError:
237 238 log.warning('ignoring repo path without read access: %s', p)
238 239 return []
239 240
240 241 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
241 242 # decode paths and suddenly returns unicode objects itself. The items it
242 243 # cannot decode are returned as strings and cause issues.
243 244 #
244 245 # Those paths are ignored here until a solid solution for path handling has
245 246 # been built.
246 247 expected_type = type(p)
247 248
248 249 def _has_correct_type(item):
249 250 if type(item) is not expected_type:
250 251 log.error(
251 252 u"Ignoring path %s since it cannot be decoded into unicode.",
252 253 # Using "repr" to make sure that we see the byte value in case
253 254 # of support.
254 255 repr(item))
255 256 return False
256 257 return True
257 258
258 259 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
259 260
260 261 return dirpaths
261 262
262 263
263 264 def _is_dir_writable(path):
264 265 """
265 266 Probe if `path` is writable.
266 267
267 268 Due to trouble on Cygwin / Windows, this is actually probing if it is
268 269 possible to create a file inside of `path`, stat does not produce reliable
269 270 results in this case.
270 271 """
271 272 try:
272 273 with tempfile.TemporaryFile(dir=path):
273 274 pass
274 275 except OSError:
275 276 return False
276 277 return True
277 278
278 279
279 280 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
280 281 """
281 282 Returns True if given path is a valid repository False otherwise.
282 283 If expect_scm param is given also, compare if given scm is the same
283 284 as expected from scm parameter. If explicit_scm is given don't try to
284 285 detect the scm, just use the given one to check if repo is valid
285 286
286 287 :param repo_name:
287 288 :param base_path:
288 289 :param expect_scm:
289 290 :param explicit_scm:
290 291
291 292 :return True: if given path is a valid repository
292 293 """
293 294 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
294 295 log.debug('Checking if `%s` is a valid path for repository', repo_name)
295 296
296 297 try:
297 298 if explicit_scm:
298 299 detected_scms = [get_scm_backend(explicit_scm)]
299 300 else:
300 301 detected_scms = get_scm(full_path)
301 302
302 303 if expect_scm:
303 304 return detected_scms[0] == expect_scm
304 305 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
305 306 return True
306 307 except VCSError:
307 308 log.debug('path: %s is not a valid repo !', full_path)
308 309 return False
309 310
310 311
311 312 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
312 313 """
313 314 Returns True if given path is a repository group, False otherwise
314 315
315 316 :param repo_name:
316 317 :param base_path:
317 318 """
318 319 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
319 320 log.debug('Checking if `%s` is a valid path for repository group',
320 321 repo_group_name)
321 322
322 323 # check if it's not a repo
323 324 if is_valid_repo(repo_group_name, base_path):
324 325 log.debug('Repo called %s exist, it is not a valid '
325 326 'repo group' % repo_group_name)
326 327 return False
327 328
328 329 try:
329 330 # we need to check bare git repos at higher level
330 331 # since we might match branches/hooks/info/objects or possible
331 332 # other things inside bare git repo
332 333 scm_ = get_scm(os.path.dirname(full_path))
333 334 log.debug('path: %s is a vcs object:%s, not valid '
334 335 'repo group' % (full_path, scm_))
335 336 return False
336 337 except VCSError:
337 338 pass
338 339
339 340 # check if it's a valid path
340 341 if skip_path_check or os.path.isdir(full_path):
341 342 log.debug('path: %s is a valid repo group !', full_path)
342 343 return True
343 344
344 345 log.debug('path: %s is not a valid repo group !', full_path)
345 346 return False
346 347
347 348
348 349 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
349 350 while True:
350 351 ok = raw_input(prompt)
351 352 if ok.lower() in ('y', 'ye', 'yes'):
352 353 return True
353 354 if ok.lower() in ('n', 'no', 'nop', 'nope'):
354 355 return False
355 356 retries = retries - 1
356 357 if retries < 0:
357 358 raise IOError
358 359 print(complaint)
359 360
360 361 # propagated from mercurial documentation
361 362 ui_sections = [
362 363 'alias', 'auth',
363 364 'decode/encode', 'defaults',
364 365 'diff', 'email',
365 366 'extensions', 'format',
366 367 'merge-patterns', 'merge-tools',
367 368 'hooks', 'http_proxy',
368 369 'smtp', 'patch',
369 370 'paths', 'profiling',
370 371 'server', 'trusted',
371 372 'ui', 'web', ]
372 373
373 374
374 375 def config_data_from_db(clear_session=True, repo=None):
375 376 """
376 377 Read the configuration data from the database and return configuration
377 378 tuples.
378 379 """
379 380 from rhodecode.model.settings import VcsSettingsModel
380 381
381 382 config = []
382 383
383 384 sa = meta.Session()
384 385 settings_model = VcsSettingsModel(repo=repo, sa=sa)
385 386
386 387 ui_settings = settings_model.get_ui_settings()
387 388
388 389 for setting in ui_settings:
389 390 if setting.active:
390 391 log.debug(
391 392 'settings ui from db: [%s] %s=%s',
392 393 setting.section, setting.key, setting.value)
393 394 config.append((
394 395 safe_str(setting.section), safe_str(setting.key),
395 396 safe_str(setting.value)))
396 397 if setting.key == 'push_ssl':
397 398 # force set push_ssl requirement to False, rhodecode
398 399 # handles that
399 400 config.append((
400 401 safe_str(setting.section), safe_str(setting.key), False))
401 402 if clear_session:
402 403 meta.Session.remove()
403 404
404 405 # TODO: mikhail: probably it makes no sense to re-read hooks information.
405 406 # It's already there and activated/deactivated
406 407 skip_entries = []
407 408 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
408 409 if 'pull' not in enabled_hook_classes:
409 410 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
410 411 if 'push' not in enabled_hook_classes:
411 412 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
412 413
413 414 config = [entry for entry in config if entry[:2] not in skip_entries]
414 415
415 416 return config
416 417
417 418
418 419 def make_db_config(clear_session=True, repo=None):
419 420 """
420 421 Create a :class:`Config` instance based on the values in the database.
421 422 """
422 423 config = Config()
423 424 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
424 425 for section, option, value in config_data:
425 426 config.set(section, option, value)
426 427 return config
427 428
428 429
429 430 def get_enabled_hook_classes(ui_settings):
430 431 """
431 432 Return the enabled hook classes.
432 433
433 434 :param ui_settings: List of ui_settings as returned
434 435 by :meth:`VcsSettingsModel.get_ui_settings`
435 436
436 437 :return: a list with the enabled hook classes. The order is not guaranteed.
437 438 :rtype: list
438 439 """
439 440 enabled_hooks = []
440 441 active_hook_keys = [
441 442 key for section, key, value, active in ui_settings
442 443 if section == 'hooks' and active]
443 444
444 445 hook_names = {
445 446 RhodeCodeUi.HOOK_PUSH: 'push',
446 447 RhodeCodeUi.HOOK_PULL: 'pull',
447 448 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
448 449 }
449 450
450 451 for key in active_hook_keys:
451 452 hook = hook_names.get(key)
452 453 if hook:
453 454 enabled_hooks.append(hook)
454 455
455 456 return enabled_hooks
456 457
457 458
458 459 def set_rhodecode_config(config):
459 460 """
460 461 Updates pylons config with new settings from database
461 462
462 463 :param config:
463 464 """
464 465 from rhodecode.model.settings import SettingsModel
465 466 app_settings = SettingsModel().get_all_settings()
466 467
467 468 for k, v in app_settings.items():
468 469 config[k] = v
469 470
470 471
471 472 def get_rhodecode_realm():
472 473 """
473 474 Return the rhodecode realm from database.
474 475 """
475 476 from rhodecode.model.settings import SettingsModel
476 477 realm = SettingsModel().get_setting_by_name('realm')
477 478 return safe_str(realm.app_settings_value)
478 479
479 480
480 481 def get_rhodecode_base_path():
481 482 """
482 483 Returns the base path. The base path is the filesystem path which points
483 484 to the repository store.
484 485 """
485 486 from rhodecode.model.settings import SettingsModel
486 487 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
487 488 return safe_str(paths_ui.ui_value)
488 489
489 490
490 491 def map_groups(path):
491 492 """
492 493 Given a full path to a repository, create all nested groups that this
493 494 repo is inside. This function creates parent-child relationships between
494 495 groups and creates default perms for all new groups.
495 496
496 497 :param paths: full path to repository
497 498 """
498 499 from rhodecode.model.repo_group import RepoGroupModel
499 500 sa = meta.Session()
500 501 groups = path.split(Repository.NAME_SEP)
501 502 parent = None
502 503 group = None
503 504
504 505 # last element is repo in nested groups structure
505 506 groups = groups[:-1]
506 507 rgm = RepoGroupModel(sa)
507 508 owner = User.get_first_super_admin()
508 509 for lvl, group_name in enumerate(groups):
509 510 group_name = '/'.join(groups[:lvl] + [group_name])
510 511 group = RepoGroup.get_by_group_name(group_name)
511 512 desc = '%s group' % group_name
512 513
513 514 # skip folders that are now removed repos
514 515 if REMOVED_REPO_PAT.match(group_name):
515 516 break
516 517
517 518 if group is None:
518 519 log.debug('creating group level: %s group_name: %s',
519 520 lvl, group_name)
520 521 group = RepoGroup(group_name, parent)
521 522 group.group_description = desc
522 523 group.user = owner
523 524 sa.add(group)
524 525 perm_obj = rgm._create_default_perms(group)
525 526 sa.add(perm_obj)
526 527 sa.flush()
527 528
528 529 parent = group
529 530 return group
530 531
531 532
532 533 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
533 534 """
534 535 maps all repos given in initial_repo_list, non existing repositories
535 536 are created, if remove_obsolete is True it also checks for db entries
536 537 that are not in initial_repo_list and removes them.
537 538
538 539 :param initial_repo_list: list of repositories found by scanning methods
539 540 :param remove_obsolete: check for obsolete entries in database
540 541 """
541 542 from rhodecode.model.repo import RepoModel
542 543 from rhodecode.model.scm import ScmModel
543 544 from rhodecode.model.repo_group import RepoGroupModel
544 545 from rhodecode.model.settings import SettingsModel
545 546
546 547 sa = meta.Session()
547 548 repo_model = RepoModel()
548 549 user = User.get_first_super_admin()
549 550 added = []
550 551
551 552 # creation defaults
552 553 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
553 554 enable_statistics = defs.get('repo_enable_statistics')
554 555 enable_locking = defs.get('repo_enable_locking')
555 556 enable_downloads = defs.get('repo_enable_downloads')
556 557 private = defs.get('repo_private')
557 558
558 559 for name, repo in initial_repo_list.items():
559 560 group = map_groups(name)
560 561 unicode_name = safe_unicode(name)
561 562 db_repo = repo_model.get_by_repo_name(unicode_name)
562 563 # found repo that is on filesystem not in RhodeCode database
563 564 if not db_repo:
564 565 log.info('repository %s not found, creating now', name)
565 566 added.append(name)
566 567 desc = (repo.description
567 568 if repo.description != 'unknown'
568 569 else '%s repository' % name)
569 570
570 571 db_repo = repo_model._create_repo(
571 572 repo_name=name,
572 573 repo_type=repo.alias,
573 574 description=desc,
574 575 repo_group=getattr(group, 'group_id', None),
575 576 owner=user,
576 577 enable_locking=enable_locking,
577 578 enable_downloads=enable_downloads,
578 579 enable_statistics=enable_statistics,
579 580 private=private,
580 581 state=Repository.STATE_CREATED
581 582 )
582 583 sa.commit()
583 584 # we added that repo just now, and make sure we updated server info
584 585 if db_repo.repo_type == 'git':
585 586 git_repo = db_repo.scm_instance()
586 587 # update repository server-info
587 588 log.debug('Running update server info')
588 589 git_repo._update_server_info()
589 590
590 591 db_repo.update_commit_cache()
591 592
592 593 config = db_repo._config
593 594 config.set('extensions', 'largefiles', '')
594 595 ScmModel().install_hooks(
595 596 db_repo.scm_instance(config=config),
596 597 repo_type=db_repo.repo_type)
597 598
598 599 removed = []
599 600 if remove_obsolete:
600 601 # remove from database those repositories that are not in the filesystem
601 602 for repo in sa.query(Repository).all():
602 603 if repo.repo_name not in initial_repo_list.keys():
603 604 log.debug("Removing non-existing repository found in db `%s`",
604 605 repo.repo_name)
605 606 try:
606 607 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
607 608 sa.commit()
608 609 removed.append(repo.repo_name)
609 610 except Exception:
610 611 # don't hold further removals on error
611 612 log.error(traceback.format_exc())
612 613 sa.rollback()
613 614
614 615 def splitter(full_repo_name):
615 616 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
616 617 gr_name = None
617 618 if len(_parts) == 2:
618 619 gr_name = _parts[0]
619 620 return gr_name
620 621
621 622 initial_repo_group_list = [splitter(x) for x in
622 623 initial_repo_list.keys() if splitter(x)]
623 624
624 625 # remove from database those repository groups that are not in the
625 626 # filesystem due to parent child relationships we need to delete them
626 627 # in a specific order of most nested first
627 628 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
628 629 nested_sort = lambda gr: len(gr.split('/'))
629 630 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
630 631 if group_name not in initial_repo_group_list:
631 632 repo_group = RepoGroup.get_by_group_name(group_name)
632 633 if (repo_group.children.all() or
633 634 not RepoGroupModel().check_exist_filesystem(
634 635 group_name=group_name, exc_on_failure=False)):
635 636 continue
636 637
637 638 log.info(
638 639 'Removing non-existing repository group found in db `%s`',
639 640 group_name)
640 641 try:
641 642 RepoGroupModel(sa).delete(group_name, fs_remove=False)
642 643 sa.commit()
643 644 removed.append(group_name)
644 645 except Exception:
645 646 # don't hold further removals on error
646 647 log.exception(
647 648 'Unable to remove repository group `%s`',
648 649 group_name)
649 650 sa.rollback()
650 651 raise
651 652
652 653 return added, removed
653 654
654 655
655 656 def get_default_cache_settings(settings):
656 657 cache_settings = {}
657 658 for key in settings.keys():
658 659 for prefix in ['beaker.cache.', 'cache.']:
659 660 if key.startswith(prefix):
660 661 name = key.split(prefix)[1].strip()
661 662 cache_settings[name] = settings[key].strip()
662 663 return cache_settings
663 664
664 665
665 666 # set cache regions for beaker so celery can utilise it
666 667 def add_cache(settings):
667 668 from rhodecode.lib import caches
668 669 cache_settings = {'regions': None}
669 670 # main cache settings used as default ...
670 671 cache_settings.update(get_default_cache_settings(settings))
671 672
672 673 if cache_settings['regions']:
673 674 for region in cache_settings['regions'].split(','):
674 675 region = region.strip()
675 676 region_settings = {}
676 677 for key, value in cache_settings.items():
677 678 if key.startswith(region):
678 679 region_settings[key.split('.')[1]] = value
679 680
680 681 caches.configure_cache_region(
681 682 region, region_settings, cache_settings)
682 683
683 684
684 685 def load_rcextensions(root_path):
685 686 import rhodecode
686 687 from rhodecode.config import conf
687 688
688 689 path = os.path.join(root_path, 'rcextensions', '__init__.py')
689 690 if os.path.isfile(path):
690 691 rcext = create_module('rc', path)
691 692 EXT = rhodecode.EXTENSIONS = rcext
692 693 log.debug('Found rcextensions now loading %s...', rcext)
693 694
694 695 # Additional mappings that are not present in the pygments lexers
695 696 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
696 697
697 698 # auto check if the module is not missing any data, set to default if is
698 699 # this will help autoupdate new feature of rcext module
699 700 #from rhodecode.config import rcextensions
700 701 #for k in dir(rcextensions):
701 702 # if not k.startswith('_') and not hasattr(EXT, k):
702 703 # setattr(EXT, k, getattr(rcextensions, k))
703 704
704 705
705 706 def get_custom_lexer(extension):
706 707 """
707 708 returns a custom lexer if it is defined in rcextensions module, or None
708 709 if there's no custom lexer defined
709 710 """
710 711 import rhodecode
711 712 from pygments import lexers
712 713 # check if we didn't define this extension as other lexer
713 714 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
714 715 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
715 716 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
716 717 return lexers.get_lexer_by_name(_lexer_name)
717 718
718 719
719 720 #==============================================================================
720 721 # TEST FUNCTIONS AND CREATORS
721 722 #==============================================================================
722 723 def create_test_index(repo_location, config):
723 724 """
724 725 Makes default test index.
725 726 """
726 727 import rc_testdata
727 728
728 729 rc_testdata.extract_search_index(
729 730 'vcs_search_index', os.path.dirname(config['search.location']))
730 731
731 732
732 733 def create_test_directory(test_path):
733 734 """
734 735 Create test directory if it doesn't exist.
735 736 """
736 737 if not os.path.isdir(test_path):
737 738 log.debug('Creating testdir %s', test_path)
738 739 os.makedirs(test_path)
739 740
740 741
741 742 def create_test_database(test_path, config):
742 743 """
743 744 Makes a fresh database.
744 745 """
745 746 from rhodecode.lib.db_manage import DbManage
746 747
747 748 # PART ONE create db
748 749 dbconf = config['sqlalchemy.db1.url']
749 750 log.debug('making test db %s', dbconf)
750 751
751 752 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
752 753 tests=True, cli_args={'force_ask': True})
753 754 dbmanage.create_tables(override=True)
754 755 dbmanage.set_db_version()
755 756 # for tests dynamically set new root paths based on generated content
756 757 dbmanage.create_settings(dbmanage.config_prompt(test_path))
757 758 dbmanage.create_default_user()
758 759 dbmanage.create_test_admin_and_users()
759 760 dbmanage.create_permissions()
760 761 dbmanage.populate_default_permissions()
761 762 Session().commit()
762 763
763 764
764 765 def create_test_repositories(test_path, config):
765 766 """
766 767 Creates test repositories in the temporary directory. Repositories are
767 768 extracted from archives within the rc_testdata package.
768 769 """
769 770 import rc_testdata
770 771 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
771 772
772 773 log.debug('making test vcs repositories')
773 774
774 775 idx_path = config['search.location']
775 776 data_path = config['cache_dir']
776 777
777 778 # clean index and data
778 779 if idx_path and os.path.exists(idx_path):
779 780 log.debug('remove %s', idx_path)
780 781 shutil.rmtree(idx_path)
781 782
782 783 if data_path and os.path.exists(data_path):
783 784 log.debug('remove %s', data_path)
784 785 shutil.rmtree(data_path)
785 786
786 787 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
787 788 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
788 789
789 790 # Note: Subversion is in the process of being integrated with the system,
790 791 # until we have a properly packed version of the test svn repository, this
791 792 # tries to copy over the repo from a package "rc_testdata"
792 793 svn_repo_path = rc_testdata.get_svn_repo_archive()
793 794 with tarfile.open(svn_repo_path) as tar:
794 795 tar.extractall(jn(test_path, SVN_REPO))
795 796
796 797
797 798 #==============================================================================
798 799 # PASTER COMMANDS
799 800 #==============================================================================
800 801 class BasePasterCommand(Command):
801 802 """
802 803 Abstract Base Class for paster commands.
803 804
804 805 The celery commands are somewhat aggressive about loading
805 806 celery.conf, and since our module sets the `CELERY_LOADER`
806 807 environment variable to our loader, we have to bootstrap a bit and
807 808 make sure we've had a chance to load the pylons config off of the
808 809 command line, otherwise everything fails.
809 810 """
810 811 min_args = 1
811 812 min_args_error = "Please provide a paster config file as an argument."
812 813 takes_config_file = 1
813 814 requires_config_file = True
814 815
815 816 def notify_msg(self, msg, log=False):
816 817 """Make a notification to user, additionally if logger is passed
817 818 it logs this action using given logger
818 819
819 820 :param msg: message that will be printed to user
820 821 :param log: logging instance, to use to additionally log this message
821 822
822 823 """
823 824 if log and isinstance(log, logging):
824 825 log(msg)
825 826
826 827 def run(self, args):
827 828 """
828 829 Overrides Command.run
829 830
830 831 Checks for a config file argument and loads it.
831 832 """
832 833 if len(args) < self.min_args:
833 834 raise BadCommand(
834 835 self.min_args_error % {'min_args': self.min_args,
835 836 'actual_args': len(args)})
836 837
837 838 # Decrement because we're going to lob off the first argument.
838 839 # @@ This is hacky
839 840 self.min_args -= 1
840 841 self.bootstrap_config(args[0])
841 842 self.update_parser()
842 843 return super(BasePasterCommand, self).run(args[1:])
843 844
844 845 def update_parser(self):
845 846 """
846 847 Abstract method. Allows for the class' parser to be updated
847 848 before the superclass' `run` method is called. Necessary to
848 849 allow options/arguments to be passed through to the underlying
849 850 celery command.
850 851 """
851 852 raise NotImplementedError("Abstract Method.")
852 853
853 854 def bootstrap_config(self, conf):
854 855 """
855 856 Loads the pylons configuration.
856 857 """
857 858 from pylons import config as pylonsconfig
858 859
859 860 self.path_to_ini_file = os.path.realpath(conf)
860 861 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
861 862 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
862 863
863 864 def _init_session(self):
864 865 """
865 866 Inits SqlAlchemy Session
866 867 """
867 868 logging.config.fileConfig(self.path_to_ini_file)
868 869 from pylons import config
869 870 from rhodecode.config.utils import initialize_database
870 871
871 872 # get to remove repos !!
872 873 add_cache(config)
873 874 initialize_database(config)
874 875
875 876
876 877 @decorator.decorator
877 878 def jsonify(func, *args, **kwargs):
878 879 """Action decorator that formats output for JSON
879 880
880 881 Given a function that will return content, this decorator will turn
881 882 the result into JSON, with a content-type of 'application/json' and
882 883 output it.
883 884
884 885 """
885 886 from pylons.decorators.util import get_pylons
886 887 from rhodecode.lib.ext_json import json
887 888 pylons = get_pylons(args)
888 889 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
889 890 data = func(*args, **kwargs)
890 891 if isinstance(data, (list, tuple)):
891 892 msg = "JSON responses with Array envelopes are susceptible to " \
892 893 "cross-site data leak attacks, see " \
893 894 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
894 895 warnings.warn(msg, Warning, 2)
895 896 log.warning(msg)
896 897 log.debug("Returning JSON wrapped action output")
897 898 return json.dumps(data, encoding='utf-8')
898 899
899 900
900 901 class PartialRenderer(object):
901 902 """
902 903 Partial renderer used to render chunks of html used in datagrids
903 904 use like::
904 905
905 906 _render = PartialRenderer('data_table/_dt_elements.html')
906 907 _render('quick_menu', args, kwargs)
907 908 PartialRenderer.h,
908 909 c,
909 910 _,
910 911 ungettext
911 912 are the template stuff initialized inside and can be re-used later
912 913
913 914 :param tmpl_name: template path relate to /templates/ dir
914 915 """
915 916
916 917 def __init__(self, tmpl_name):
917 918 import rhodecode
918 919 from pylons import request, tmpl_context as c
919 920 from pylons.i18n.translation import _, ungettext
920 921 from rhodecode.lib import helpers as h
921 922
922 923 self.tmpl_name = tmpl_name
923 924 self.rhodecode = rhodecode
924 925 self.c = c
925 926 self._ = _
926 927 self.ungettext = ungettext
927 928 self.h = h
928 929 self.request = request
929 930
930 931 def _mako_lookup(self):
931 932 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
932 933 return _tmpl_lookup.get_template(self.tmpl_name)
933 934
934 935 def _update_kwargs_for_render(self, kwargs):
935 936 """
936 937 Inject params required for Mako rendering
937 938 """
938 939 _kwargs = {
939 940 '_': self._,
940 941 'h': self.h,
941 942 'c': self.c,
942 943 'request': self.request,
943 944 'ungettext': self.ungettext,
944 945 }
945 946 _kwargs.update(kwargs)
946 947 return _kwargs
947 948
948 949 def _render_with_exc(self, render_func, args, kwargs):
949 950 try:
950 951 return render_func.render(*args, **kwargs)
951 952 except:
952 953 log.error(exceptions.text_error_template().render())
953 954 raise
954 955
955 956 def _get_template(self, template_obj, def_name):
956 957 if def_name:
957 958 tmpl = template_obj.get_def(def_name)
958 959 else:
959 960 tmpl = template_obj
960 961 return tmpl
961 962
962 963 def render(self, def_name, *args, **kwargs):
963 964 lookup_obj = self._mako_lookup()
964 965 tmpl = self._get_template(lookup_obj, def_name=def_name)
965 966 kwargs = self._update_kwargs_for_render(kwargs)
966 967 return self._render_with_exc(tmpl, args, kwargs)
967 968
968 969 def __call__(self, tmpl, *args, **kwargs):
969 970 return self.render(tmpl, *args, **kwargs)
970 971
971 972
972 973 def password_changed(auth_user, session):
973 974 # Never report password change in case of default user or anonymous user.
974 975 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
975 976 return False
976 977
977 978 password_hash = md5(auth_user.password) if auth_user.password else None
978 979 rhodecode_user = session.get('rhodecode_user', {})
979 980 session_password_hash = rhodecode_user.get('password', '')
980 981 return password_hash != session_password_hash
981 982
982 983
983 984 def read_opensource_licenses():
984 985 global _license_cache
985 986
986 987 if not _license_cache:
987 988 licenses = pkg_resources.resource_string(
988 989 'rhodecode', 'config/licenses.json')
989 990 _license_cache = json.loads(licenses)
990 991
991 992 return _license_cache
992 993
993 994
994 995 def get_registry(request):
995 996 """
996 997 Utility to get the pyramid registry from a request. During migration to
997 998 pyramid we sometimes want to use the pyramid registry from pylons context.
998 999 Therefore this utility returns `request.registry` for pyramid requests and
999 1000 uses `get_current_registry()` for pylons requests.
1000 1001 """
1001 1002 try:
1002 1003 return request.registry
1003 1004 except AttributeError:
1004 1005 return get_current_registry()
1005 1006
1006 1007
1007 1008 def generate_platform_uuid():
1008 1009 """
1009 1010 Generates platform UUID based on it's name
1010 1011 """
1011 1012 import platform
1012 1013
1013 1014 try:
1014 1015 uuid_list = [platform.platform()]
1015 1016 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
1016 1017 except Exception as e:
1017 1018 log.error('Failed to generate host uuid: %s' % e)
1018 1019 return 'UNDEFINED'
General Comments 0
You need to be logged in to leave comments. Login now