##// END OF EJS Templates
mercurial: refactor how we fetch default largefile store....
marcink -
r1562:19b546f8 default
parent child Browse files
Show More
@@ -1,600 +1,601 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 23 of database as well as for migration operations
24 24 """
25 25
26 26 import os
27 27 import sys
28 28 import time
29 29 import uuid
30 30 import logging
31 31 import getpass
32 32 from os.path import dirname as dn, join as jn
33 33
34 34 from sqlalchemy.engine import create_engine
35 35
36 36 from rhodecode import __dbversion__
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.db import (
40 40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 42 from rhodecode.model.meta import Session, Base
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.repo_group import RepoGroupModel
46 46 from rhodecode.model.settings import SettingsModel
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 def notify(msg):
53 53 """
54 54 Notification for migrations messages
55 55 """
56 56 ml = len(msg) + (4 * 2)
57 57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
58 58
59 59
60 60 class DbManage(object):
61 61
62 62 def __init__(self, log_sql, dbconf, root, tests=False,
63 63 SESSION=None, cli_args={}):
64 64 self.dbname = dbconf.split('/')[-1]
65 65 self.tests = tests
66 66 self.root = root
67 67 self.dburi = dbconf
68 68 self.log_sql = log_sql
69 69 self.db_exists = False
70 70 self.cli_args = cli_args
71 71 self.init_db(SESSION=SESSION)
72 72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73 73
74 74 def get_ask_ok_func(self, param):
75 75 if param not in [None]:
76 76 # return a function lambda that has a default set to param
77 77 return lambda *args, **kwargs: param
78 78 else:
79 79 from rhodecode.lib.utils import ask_ok
80 80 return ask_ok
81 81
82 82 def init_db(self, SESSION=None):
83 83 if SESSION:
84 84 self.sa = SESSION
85 85 else:
86 86 # init new sessions
87 87 engine = create_engine(self.dburi, echo=self.log_sql)
88 88 init_model(engine)
89 89 self.sa = Session()
90 90
91 91 def create_tables(self, override=False):
92 92 """
93 93 Create a auth database
94 94 """
95 95
96 96 log.info("Existing database with the same name is going to be destroyed.")
97 97 log.info("Setup command will run DROP ALL command on that database.")
98 98 if self.tests:
99 99 destroy = True
100 100 else:
101 101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 102 if not destroy:
103 103 log.info('Nothing done.')
104 104 sys.exit(0)
105 105 if destroy:
106 106 Base.metadata.drop_all()
107 107
108 108 checkfirst = not override
109 109 Base.metadata.create_all(checkfirst=checkfirst)
110 110 log.info('Created tables for %s' % self.dbname)
111 111
112 112 def set_db_version(self):
113 113 ver = DbMigrateVersion()
114 114 ver.version = __dbversion__
115 115 ver.repository_id = 'rhodecode_db_migrations'
116 116 ver.repository_path = 'versions'
117 117 self.sa.add(ver)
118 118 log.info('db version set to: %s' % __dbversion__)
119 119
120 120 def run_pre_migration_tasks(self):
121 121 """
122 122 Run various tasks before actually doing migrations
123 123 """
124 124 # delete cache keys on each upgrade
125 125 total = CacheKey.query().count()
126 126 log.info("Deleting (%s) cache keys now...", total)
127 127 CacheKey.delete_all_cache()
128 128
129 129 def upgrade(self):
130 130 """
131 131 Upgrades given database schema to given revision following
132 132 all needed steps, to perform the upgrade
133 133
134 134 """
135 135
136 136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 138 DatabaseNotControlledError
139 139
140 140 if 'sqlite' in self.dburi:
141 141 print (
142 142 '********************** WARNING **********************\n'
143 143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 144 'Earlier versions are known to fail on some migrations\n'
145 145 '*****************************************************\n')
146 146
147 147 upgrade = self.ask_ok(
148 148 'You are about to perform a database upgrade. Make '
149 149 'sure you have backed up your database. '
150 150 'Continue ? [y/n]')
151 151 if not upgrade:
152 152 log.info('No upgrade performed')
153 153 sys.exit(0)
154 154
155 155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 156 'rhodecode/lib/dbmigrate')
157 157 db_uri = self.dburi
158 158
159 159 try:
160 160 curr_version = api.db_version(db_uri, repository_path)
161 161 msg = ('Found current database under version '
162 162 'control with version %s' % curr_version)
163 163
164 164 except (RuntimeError, DatabaseNotControlledError):
165 165 curr_version = 1
166 166 msg = ('Current database is not under version control. Setting '
167 167 'as version %s' % curr_version)
168 168 api.version_control(db_uri, repository_path, curr_version)
169 169
170 170 notify(msg)
171 171
172 172 self.run_pre_migration_tasks()
173 173
174 174 if curr_version == __dbversion__:
175 175 log.info('This database is already at the newest version')
176 176 sys.exit(0)
177 177
178 178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
179 179 notify('attempting to upgrade database from '
180 180 'version %s to version %s' % (curr_version, __dbversion__))
181 181
182 182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 183 _step = None
184 184 for step in upgrade_steps:
185 185 notify('performing upgrade step %s' % step)
186 186 time.sleep(0.5)
187 187
188 188 api.upgrade(db_uri, repository_path, step)
189 189 self.sa.rollback()
190 190 notify('schema upgrade for step %s completed' % (step,))
191 191
192 192 _step = step
193 193
194 194 notify('upgrade to version %s successful' % _step)
195 195
196 196 def fix_repo_paths(self):
197 197 """
198 198 Fixes an old RhodeCode version path into new one without a '*'
199 199 """
200 200
201 201 paths = self.sa.query(RhodeCodeUi)\
202 202 .filter(RhodeCodeUi.ui_key == '/')\
203 203 .scalar()
204 204
205 205 paths.ui_value = paths.ui_value.replace('*', '')
206 206
207 207 try:
208 208 self.sa.add(paths)
209 209 self.sa.commit()
210 210 except Exception:
211 211 self.sa.rollback()
212 212 raise
213 213
214 214 def fix_default_user(self):
215 215 """
216 216 Fixes an old default user with some 'nicer' default values,
217 217 used mostly for anonymous access
218 218 """
219 219 def_user = self.sa.query(User)\
220 220 .filter(User.username == User.DEFAULT_USER)\
221 221 .one()
222 222
223 223 def_user.name = 'Anonymous'
224 224 def_user.lastname = 'User'
225 225 def_user.email = User.DEFAULT_USER_EMAIL
226 226
227 227 try:
228 228 self.sa.add(def_user)
229 229 self.sa.commit()
230 230 except Exception:
231 231 self.sa.rollback()
232 232 raise
233 233
234 234 def fix_settings(self):
235 235 """
236 236 Fixes rhodecode settings and adds ga_code key for google analytics
237 237 """
238 238
239 239 hgsettings3 = RhodeCodeSetting('ga_code', '')
240 240
241 241 try:
242 242 self.sa.add(hgsettings3)
243 243 self.sa.commit()
244 244 except Exception:
245 245 self.sa.rollback()
246 246 raise
247 247
248 248 def create_admin_and_prompt(self):
249 249
250 250 # defaults
251 251 defaults = self.cli_args
252 252 username = defaults.get('username')
253 253 password = defaults.get('password')
254 254 email = defaults.get('email')
255 255
256 256 if username is None:
257 257 username = raw_input('Specify admin username:')
258 258 if password is None:
259 259 password = self._get_admin_password()
260 260 if not password:
261 261 # second try
262 262 password = self._get_admin_password()
263 263 if not password:
264 264 sys.exit()
265 265 if email is None:
266 266 email = raw_input('Specify admin email:')
267 267 api_key = self.cli_args.get('api_key')
268 268 self.create_user(username, password, email, True,
269 269 strict_creation_check=False,
270 270 api_key=api_key)
271 271
272 272 def _get_admin_password(self):
273 273 password = getpass.getpass('Specify admin password '
274 274 '(min 6 chars):')
275 275 confirm = getpass.getpass('Confirm password:')
276 276
277 277 if password != confirm:
278 278 log.error('passwords mismatch')
279 279 return False
280 280 if len(password) < 6:
281 281 log.error('password is too short - use at least 6 characters')
282 282 return False
283 283
284 284 return password
285 285
286 286 def create_test_admin_and_users(self):
287 287 log.info('creating admin and regular test users')
288 288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
289 289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
290 290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
291 291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
292 292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293 293
294 294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
296 296
297 297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
299 299
300 300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
302 302
303 303 def create_ui_settings(self, repo_store_path):
304 304 """
305 305 Creates ui settings, fills out hooks
306 306 and disables dotencode
307 307 """
308 308 settings_model = SettingsModel(sa=self.sa)
309 from rhodecode.lib.vcs.backends.hg import largefiles_store
309 310
310 311 # Build HOOKS
311 312 hooks = [
312 313 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
313 314
314 315 # HG
315 316 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
316 317 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
317 318 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
318 319 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
319 320 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
320 321
321 322 ]
322 323
323 324 for key, value in hooks:
324 325 hook_obj = settings_model.get_ui_by_key(key)
325 326 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
326 327 hooks2.ui_section = 'hooks'
327 328 hooks2.ui_key = key
328 329 hooks2.ui_value = value
329 330 self.sa.add(hooks2)
330 331
331 332 # enable largefiles
332 333 largefiles = RhodeCodeUi()
333 334 largefiles.ui_section = 'extensions'
334 335 largefiles.ui_key = 'largefiles'
335 336 largefiles.ui_value = ''
336 337 self.sa.add(largefiles)
337 338
338 339 # set default largefiles cache dir, defaults to
339 # /repo location/.cache/largefiles
340 # /repo_store_location/.cache/largefiles
340 341 largefiles = RhodeCodeUi()
341 342 largefiles.ui_section = 'largefiles'
342 343 largefiles.ui_key = 'usercache'
343 largefiles.ui_value = os.path.join(repo_store_path, '.cache',
344 'largefiles')
344 largefiles.ui_value = largefiles_store(repo_store_path)
345
345 346 self.sa.add(largefiles)
346 347
347 348 # enable hgsubversion disabled by default
348 349 hgsubversion = RhodeCodeUi()
349 350 hgsubversion.ui_section = 'extensions'
350 351 hgsubversion.ui_key = 'hgsubversion'
351 352 hgsubversion.ui_value = ''
352 353 hgsubversion.ui_active = False
353 354 self.sa.add(hgsubversion)
354 355
355 356 # enable hggit disabled by default
356 357 hggit = RhodeCodeUi()
357 358 hggit.ui_section = 'extensions'
358 359 hggit.ui_key = 'hggit'
359 360 hggit.ui_value = ''
360 361 hggit.ui_active = False
361 362 self.sa.add(hggit)
362 363
363 364 # set svn branch defaults
364 365 branches = ["/branches/*", "/trunk"]
365 366 tags = ["/tags/*"]
366 367
367 368 for branch in branches:
368 369 settings_model.create_ui_section_value(
369 370 RhodeCodeUi.SVN_BRANCH_ID, branch)
370 371
371 372 for tag in tags:
372 373 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
373 374
374 375 def create_auth_plugin_options(self, skip_existing=False):
375 376 """
376 377 Create default auth plugin settings, and make it active
377 378
378 379 :param skip_existing:
379 380 """
380 381
381 382 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
382 383 ('auth_rhodecode_enabled', 'True', 'bool')]:
383 384 if (skip_existing and
384 385 SettingsModel().get_setting_by_name(k) is not None):
385 386 log.debug('Skipping option %s' % k)
386 387 continue
387 388 setting = RhodeCodeSetting(k, v, t)
388 389 self.sa.add(setting)
389 390
390 391 def create_default_options(self, skip_existing=False):
391 392 """Creates default settings"""
392 393
393 394 for k, v, t in [
394 395 ('default_repo_enable_locking', False, 'bool'),
395 396 ('default_repo_enable_downloads', False, 'bool'),
396 397 ('default_repo_enable_statistics', False, 'bool'),
397 398 ('default_repo_private', False, 'bool'),
398 399 ('default_repo_type', 'hg', 'unicode')]:
399 400
400 401 if (skip_existing and
401 402 SettingsModel().get_setting_by_name(k) is not None):
402 403 log.debug('Skipping option %s' % k)
403 404 continue
404 405 setting = RhodeCodeSetting(k, v, t)
405 406 self.sa.add(setting)
406 407
407 408 def fixup_groups(self):
408 409 def_usr = User.get_default_user()
409 410 for g in RepoGroup.query().all():
410 411 g.group_name = g.get_new_name(g.name)
411 412 self.sa.add(g)
412 413 # get default perm
413 414 default = UserRepoGroupToPerm.query()\
414 415 .filter(UserRepoGroupToPerm.group == g)\
415 416 .filter(UserRepoGroupToPerm.user == def_usr)\
416 417 .scalar()
417 418
418 419 if default is None:
419 420 log.debug('missing default permission for group %s adding' % g)
420 421 perm_obj = RepoGroupModel()._create_default_perms(g)
421 422 self.sa.add(perm_obj)
422 423
423 424 def reset_permissions(self, username):
424 425 """
425 426 Resets permissions to default state, useful when old systems had
426 427 bad permissions, we must clean them up
427 428
428 429 :param username:
429 430 """
430 431 default_user = User.get_by_username(username)
431 432 if not default_user:
432 433 return
433 434
434 435 u2p = UserToPerm.query()\
435 436 .filter(UserToPerm.user == default_user).all()
436 437 fixed = False
437 438 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
438 439 for p in u2p:
439 440 Session().delete(p)
440 441 fixed = True
441 442 self.populate_default_permissions()
442 443 return fixed
443 444
444 445 def update_repo_info(self):
445 446 RepoModel.update_repoinfo()
446 447
447 448 def config_prompt(self, test_repo_path='', retries=3):
448 449 defaults = self.cli_args
449 450 _path = defaults.get('repos_location')
450 451 if retries == 3:
451 452 log.info('Setting up repositories config')
452 453
453 454 if _path is not None:
454 455 path = _path
455 456 elif not self.tests and not test_repo_path:
456 457 path = raw_input(
457 458 'Enter a valid absolute path to store repositories. '
458 459 'All repositories in that path will be added automatically:'
459 460 )
460 461 else:
461 462 path = test_repo_path
462 463 path_ok = True
463 464
464 465 # check proper dir
465 466 if not os.path.isdir(path):
466 467 path_ok = False
467 468 log.error('Given path %s is not a valid directory' % (path,))
468 469
469 470 elif not os.path.isabs(path):
470 471 path_ok = False
471 472 log.error('Given path %s is not an absolute path' % (path,))
472 473
473 474 # check if path is at least readable.
474 475 if not os.access(path, os.R_OK):
475 476 path_ok = False
476 477 log.error('Given path %s is not readable' % (path,))
477 478
478 479 # check write access, warn user about non writeable paths
479 480 elif not os.access(path, os.W_OK) and path_ok:
480 481 log.warning('No write permission to given path %s' % (path,))
481 482
482 483 q = ('Given path %s is not writeable, do you want to '
483 484 'continue with read only mode ? [y/n]' % (path,))
484 485 if not self.ask_ok(q):
485 486 log.error('Canceled by user')
486 487 sys.exit(-1)
487 488
488 489 if retries == 0:
489 490 sys.exit('max retries reached')
490 491 if not path_ok:
491 492 retries -= 1
492 493 return self.config_prompt(test_repo_path, retries)
493 494
494 495 real_path = os.path.normpath(os.path.realpath(path))
495 496
496 497 if real_path != os.path.normpath(path):
497 498 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
498 499 'given path as %s ? [y/n]') % (real_path,)
499 500 if not self.ask_ok(q):
500 501 log.error('Canceled by user')
501 502 sys.exit(-1)
502 503
503 504 return real_path
504 505
505 506 def create_settings(self, path):
506 507
507 508 self.create_ui_settings(path)
508 509
509 510 ui_config = [
510 511 ('web', 'push_ssl', 'false'),
511 512 ('web', 'allow_archive', 'gz zip bz2'),
512 513 ('web', 'allow_push', '*'),
513 514 ('web', 'baseurl', '/'),
514 515 ('paths', '/', path),
515 516 ('phases', 'publish', 'true')
516 517 ]
517 518 for section, key, value in ui_config:
518 519 ui_conf = RhodeCodeUi()
519 520 setattr(ui_conf, 'ui_section', section)
520 521 setattr(ui_conf, 'ui_key', key)
521 522 setattr(ui_conf, 'ui_value', value)
522 523 self.sa.add(ui_conf)
523 524
524 525 # rhodecode app settings
525 526 settings = [
526 527 ('realm', 'RhodeCode', 'unicode'),
527 528 ('title', '', 'unicode'),
528 529 ('pre_code', '', 'unicode'),
529 530 ('post_code', '', 'unicode'),
530 531 ('show_public_icon', True, 'bool'),
531 532 ('show_private_icon', True, 'bool'),
532 533 ('stylify_metatags', False, 'bool'),
533 534 ('dashboard_items', 100, 'int'),
534 535 ('admin_grid_items', 25, 'int'),
535 536 ('show_version', True, 'bool'),
536 537 ('use_gravatar', False, 'bool'),
537 538 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
538 539 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
539 540 ('support_url', '', 'unicode'),
540 541 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
541 542 ('show_revision_number', True, 'bool'),
542 543 ('show_sha_length', 12, 'int'),
543 544 ]
544 545
545 546 for key, val, type_ in settings:
546 547 sett = RhodeCodeSetting(key, val, type_)
547 548 self.sa.add(sett)
548 549
549 550 self.create_auth_plugin_options()
550 551 self.create_default_options()
551 552
552 553 log.info('created ui config')
553 554
554 555 def create_user(self, username, password, email='', admin=False,
555 556 strict_creation_check=True, api_key=None):
556 557 log.info('creating user %s' % username)
557 558 user = UserModel().create_or_update(
558 559 username, password, email, firstname='RhodeCode', lastname='Admin',
559 560 active=True, admin=admin, extern_type="rhodecode",
560 561 strict_creation_check=strict_creation_check)
561 562
562 563 if api_key:
563 564 log.info('setting a provided api key for the user %s', username)
564 565 from rhodecode.model.auth_token import AuthTokenModel
565 566 AuthTokenModel().create(
566 567 user=user, description='BUILTIN TOKEN')
567 568
568 569 def create_default_user(self):
569 570 log.info('creating default user')
570 571 # create default user for handling default permissions.
571 572 user = UserModel().create_or_update(username=User.DEFAULT_USER,
572 573 password=str(uuid.uuid1())[:20],
573 574 email=User.DEFAULT_USER_EMAIL,
574 575 firstname='Anonymous',
575 576 lastname='User',
576 577 strict_creation_check=False)
577 578 # based on configuration options activate/deactive this user which
578 579 # controlls anonymous access
579 580 if self.cli_args.get('public_access') is False:
580 581 log.info('Public access disabled')
581 582 user.active = False
582 583 Session().add(user)
583 584 Session().commit()
584 585
585 586 def create_permissions(self):
586 587 """
587 588 Creates all permissions defined in the system
588 589 """
589 590 # module.(access|create|change|delete)_[name]
590 591 # module.(none|read|write|admin)
591 592 log.info('creating permissions')
592 593 PermissionModel(self.sa).create_permissions()
593 594
594 595 def populate_default_permissions(self):
595 596 """
596 597 Populate default permissions. It will create only the default
597 598 permissions that are missing, and not alter already defined ones
598 599 """
599 600 log.info('creating default user permissions')
600 601 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,48 +1,56 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 HG module
23 23 """
24 import os
24 25 import logging
25 26
26 27 from rhodecode.lib.vcs import connection
27 28 from rhodecode.lib.vcs.backends.hg.commit import MercurialCommit
28 29 from rhodecode.lib.vcs.backends.hg.inmemory import MercurialInMemoryCommit
29 30 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
30 31
31 32
32 33 log = logging.getLogger(__name__)
33 34
34 35
35 36 def discover_hg_version(raise_on_exc=False):
36 37 """
37 38 Returns the string as it was returned by running 'git --version'
38 39
39 40 It will return an empty string in case the connection is not initialized
40 41 or no vcsserver is available.
41 42 """
42 43 try:
43 44 return connection.Hg.discover_hg_version()
44 45 except Exception:
45 46 log.warning("Failed to discover the HG version", exc_info=True)
46 47 if raise_on_exc:
47 48 raise
48 49 return ''
50
51
52 def largefiles_store(base_location):
53 """
54 Return a largefile store relative to base_location
55 """
56 return os.path.join(base_location, '.cache', 'largefiles')
@@ -1,275 +1,282 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 from subprocess32 import Popen, PIPE
22 22 import os
23 23 import shutil
24 24 import sys
25 25 import tempfile
26 26
27 27 import pytest
28 28 from sqlalchemy.engine import url
29 29
30 30 from rhodecode.tests.fixture import TestINI
31 31
32 32
33 33 def _get_dbs_from_metafunc(metafunc):
34 34 if hasattr(metafunc.function, 'dbs'):
35 35 # Supported backends by this test function, created from
36 36 # pytest.mark.dbs
37 37 backends = metafunc.function.dbs.args
38 38 else:
39 39 backends = metafunc.config.getoption('--dbs')
40 40 return backends
41 41
42 42
43 43 def pytest_generate_tests(metafunc):
44 44 # Support test generation based on --dbs parameter
45 45 if 'db_backend' in metafunc.fixturenames:
46 46 requested_backends = set(metafunc.config.getoption('--dbs'))
47 47 backends = _get_dbs_from_metafunc(metafunc)
48 48 backends = requested_backends.intersection(backends)
49 49 # TODO: johbo: Disabling a backend did not work out with
50 50 # parametrization, find better way to achieve this.
51 51 if not backends:
52 52 metafunc.function._skip = True
53 53 metafunc.parametrize('db_backend_name', backends)
54 54
55 55
56 56 def pytest_collection_modifyitems(session, config, items):
57 57 remaining = [
58 58 i for i in items if not getattr(i.obj, '_skip', False)]
59 59 items[:] = remaining
60 60
61 61
62 62 @pytest.fixture
63 63 def db_backend(
64 64 request, db_backend_name, pylons_config, tmpdir_factory):
65 65 basetemp = tmpdir_factory.getbasetemp().strpath
66 66 klass = _get_backend(db_backend_name)
67 67
68 68 option_name = '--{}-connection-string'.format(db_backend_name)
69 69 connection_string = request.config.getoption(option_name) or None
70 70
71 71 return klass(
72 72 config_file=pylons_config, basetemp=basetemp,
73 73 connection_string=connection_string)
74 74
75 75
76 76 def _get_backend(backend_type):
77 77 return {
78 78 'sqlite': SQLiteDBBackend,
79 79 'postgres': PostgresDBBackend,
80 80 'mysql': MySQLDBBackend,
81 81 '': EmptyDBBackend
82 82 }[backend_type]
83 83
84 84
85 85 class DBBackend(object):
86 86 _store = os.path.dirname(os.path.abspath(__file__))
87 87 _type = None
88 88 _base_ini_config = [{'app:main': {'vcs.start_server': 'false'}}]
89 89 _db_url = [{'app:main': {'sqlalchemy.db1.url': ''}}]
90 90 _base_db_name = 'rhodecode_test_db_backend'
91 91
92 92 def __init__(
93 93 self, config_file, db_name=None, basetemp=None,
94 94 connection_string=None):
95
96 from rhodecode.lib.vcs.backends.hg import largefiles_store
97
95 98 self.fixture_store = os.path.join(self._store, self._type)
96 99 self.db_name = db_name or self._base_db_name
97 100 self._base_ini_file = config_file
98 101 self.stderr = ''
99 102 self.stdout = ''
100 103 self._basetemp = basetemp or tempfile.gettempdir()
101 104 self._repos_location = os.path.join(self._basetemp, 'rc_test_repos')
105 self._repos_hg_largefiles_store = largefiles_store(self._basetemp)
102 106 self.connection_string = connection_string
103 107
104 108 @property
105 109 def connection_string(self):
106 110 return self._connection_string
107 111
108 112 @connection_string.setter
109 113 def connection_string(self, new_connection_string):
110 114 if not new_connection_string:
111 115 new_connection_string = self.get_default_connection_string()
112 116 else:
113 117 new_connection_string = new_connection_string.format(
114 118 db_name=self.db_name)
115 119 url_parts = url.make_url(new_connection_string)
116 120 self._connection_string = new_connection_string
117 121 self.user = url_parts.username
118 122 self.password = url_parts.password
119 123 self.host = url_parts.host
120 124
121 125 def get_default_connection_string(self):
122 126 raise NotImplementedError('default connection_string is required.')
123 127
124 128 def execute(self, cmd, env=None, *args):
125 129 """
126 130 Runs command on the system with given ``args``.
127 131 """
128 132
129 133 command = cmd + ' ' + ' '.join(args)
130 134 sys.stdout.write(command)
131 135
132 136 # Tell Python to use UTF-8 encoding out stdout
133 137 _env = os.environ.copy()
134 138 _env['PYTHONIOENCODING'] = 'UTF-8'
135 139 if env:
136 140 _env.update(env)
137 141 self.p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, env=_env)
138 142 self.stdout, self.stderr = self.p.communicate()
139 143 sys.stdout.write('COMMAND:'+command+'\n')
140 144 sys.stdout.write(self.stdout)
141 145 return self.stdout, self.stderr
142 146
143 147 def assert_returncode_success(self):
144 148 if not self.p.returncode == 0:
145 149 print(self.stderr)
146 150 raise AssertionError('non 0 retcode:{}'.format(self.p.returncode))
147 151
148 152 def setup_rhodecode_db(self, ini_params=None, env=None):
149 153 if not ini_params:
150 154 ini_params = self._base_ini_config
151 155
152 156 ini_params.extend(self._db_url)
153 157 with TestINI(self._base_ini_file, ini_params,
154 158 self._type, destroy=True) as _ini_file:
155 159 if not os.path.isdir(self._repos_location):
156 160 os.makedirs(self._repos_location)
161 if not os.path.isdir(self._repos_hg_largefiles_store):
162 os.makedirs(self._repos_hg_largefiles_store)
163
157 164 self.execute(
158 165 "paster setup-rhodecode {0} --user=marcink "
159 166 "--email=marcin@rhodeocode.com --password={1} "
160 167 "--repos={2} --force-yes".format(
161 168 _ini_file, 'qweqwe', self._repos_location), env=env)
162 169
163 170 def upgrade_database(self, ini_params=None):
164 171 if not ini_params:
165 172 ini_params = self._base_ini_config
166 173 ini_params.extend(self._db_url)
167 174
168 175 test_ini = TestINI(
169 176 self._base_ini_file, ini_params, self._type, destroy=True)
170 177 with test_ini as ini_file:
171 178 if not os.path.isdir(self._repos_location):
172 179 os.makedirs(self._repos_location)
173 180 self.execute(
174 181 "paster upgrade-db {} --force-yes".format(ini_file))
175 182
176 183 def setup_db(self):
177 184 raise NotImplementedError
178 185
179 186 def teardown_db(self):
180 187 raise NotImplementedError
181 188
182 189 def import_dump(self, dumpname):
183 190 raise NotImplementedError
184 191
185 192
186 193 class EmptyDBBackend(DBBackend):
187 194 _type = ''
188 195
189 196 def setup_db(self):
190 197 pass
191 198
192 199 def teardown_db(self):
193 200 pass
194 201
195 202 def import_dump(self, dumpname):
196 203 pass
197 204
198 205 def assert_returncode_success(self):
199 206 assert True
200 207
201 208
202 209 class SQLiteDBBackend(DBBackend):
203 210 _type = 'sqlite'
204 211
205 212 def get_default_connection_string(self):
206 213 return 'sqlite:///{}/{}.sqlite'.format(self._basetemp, self.db_name)
207 214
208 215 def setup_db(self):
209 216 # dump schema for tests
210 217 # cp -v $TEST_DB_NAME
211 218 self._db_url = [{'app:main': {
212 219 'sqlalchemy.db1.url': self.connection_string}}]
213 220
214 221 def import_dump(self, dumpname):
215 222 dump = os.path.join(self.fixture_store, dumpname)
216 223 shutil.copy(
217 224 dump,
218 225 os.path.join(self._basetemp, '{0.db_name}.sqlite'.format(self)))
219 226
220 227 def teardown_db(self):
221 228 self.execute("rm -rf {}.sqlite".format(
222 229 os.path.join(self._basetemp, self.db_name)))
223 230
224 231
225 232 class MySQLDBBackend(DBBackend):
226 233 _type = 'mysql'
227 234
228 235 def get_default_connection_string(self):
229 236 return 'mysql://root:qweqwe@127.0.0.1/{}'.format(self.db_name)
230 237
231 238 def setup_db(self):
232 239 # dump schema for tests
233 240 # mysqldump -uroot -pqweqwe $TEST_DB_NAME
234 241 self._db_url = [{'app:main': {
235 242 'sqlalchemy.db1.url': self.connection_string}}]
236 243 self.execute("mysql -v -u{} -p{} -e 'create database '{}';'".format(
237 244 self.user, self.password, self.db_name))
238 245
239 246 def import_dump(self, dumpname):
240 247 dump = os.path.join(self.fixture_store, dumpname)
241 248 self.execute("mysql -u{} -p{} {} < {}".format(
242 249 self.user, self.password, self.db_name, dump))
243 250
244 251 def teardown_db(self):
245 252 self.execute("mysql -v -u{} -p{} -e 'drop database '{}';'".format(
246 253 self.user, self.password, self.db_name))
247 254
248 255
249 256 class PostgresDBBackend(DBBackend):
250 257 _type = 'postgres'
251 258
252 259 def get_default_connection_string(self):
253 260 return 'postgresql://postgres:qweqwe@localhost/{}'.format(self.db_name)
254 261
255 262 def setup_db(self):
256 263 # dump schema for tests
257 264 # pg_dump -U postgres -h localhost $TEST_DB_NAME
258 265 self._db_url = [{'app:main': {
259 266 'sqlalchemy.db1.url':
260 267 self.connection_string}}]
261 268 self.execute("PGPASSWORD={} psql -U {} -h localhost "
262 269 "-c 'create database '{}';'".format(
263 270 self.password, self.user, self.db_name))
264 271
265 272 def teardown_db(self):
266 273 self.execute("PGPASSWORD={} psql -U {} -h localhost "
267 274 "-c 'drop database if exists '{}';'".format(
268 275 self.password, self.user, self.db_name))
269 276
270 277 def import_dump(self, dumpname):
271 278 dump = os.path.join(self.fixture_store, dumpname)
272 279 self.execute(
273 280 "PGPASSWORD={} psql -U {} -h localhost -d {} -1 "
274 281 "-f {}".format(
275 282 self.password, self.user, self.db_name, dump))
General Comments 0
You need to be logged in to leave comments. Login now