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