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