db_manage.py
553 lines
| 18.9 KiB
| text/x-python
|
PythonLexer
r835 | # -*- coding: utf-8 -*- | |||
""" | ||||
rhodecode.lib.db_manage | ||||
~~~~~~~~~~~~~~~~~~~~~~~ | ||||
r838 | Database creation, and setup module for RhodeCode. Used for creation | |||
of database as well as for migration operations | ||||
r1203 | ||||
r835 | :created_on: Apr 10, 2010 | |||
:author: marcink | ||||
r1824 | :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com> | |||
r835 | :license: GPLv3, see COPYING for more details. | |||
""" | ||||
r1206 | # This program is free software: you can redistribute it and/or modify | |||
# it under the terms of the GNU General Public License as published by | ||||
# the Free Software Foundation, either version 3 of the License, or | ||||
# (at your option) any later version. | ||||
r1203 | # | |||
r547 | # This program is distributed in the hope that it will be useful, | |||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
r1203 | # | |||
r547 | # You should have received a copy of the GNU General Public License | |||
r1206 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
r547 | ||||
import os | ||||
import sys | ||||
import uuid | ||||
r835 | import logging | |||
from os.path import dirname as dn, join as jn | ||||
from rhodecode import __dbversion__ | ||||
from rhodecode.model import meta | ||||
r547 | ||||
r1634 | from rhodecode.model.user import UserModel | |||
r547 | from rhodecode.lib.utils import ask_ok | |||
from rhodecode.model import init_model | ||||
r1266 | from rhodecode.model.db import User, Permission, RhodeCodeUi, \ | |||
r1985 | RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\ | |||
UserRepoGroupToPerm | ||||
r835 | ||||
r547 | from sqlalchemy.engine import create_engine | |||
r1985 | from rhodecode.model.repos_group import ReposGroupModel | |||
r835 | ||||
r547 | log = logging.getLogger(__name__) | |||
r1266 | ||||
r547 | class DbManage(object): | |||
r781 | def __init__(self, log_sql, dbconf, root, tests=False): | |||
self.dbname = dbconf.split('/')[-1] | ||||
r547 | self.tests = tests | |||
r552 | self.root = root | |||
r781 | self.dburi = dbconf | |||
r907 | self.log_sql = log_sql | |||
self.db_exists = False | ||||
self.init_db() | ||||
def init_db(self): | ||||
engine = create_engine(self.dburi, echo=self.log_sql) | ||||
r547 | init_model(engine) | |||
r2662 | self.sa = meta.Session() | |||
r629 | ||||
r2651 | def create_tables(self, override=False, defaults={}): | |||
r1783 | """ | |||
Create a auth database | ||||
r547 | """ | |||
r2651 | quiet = defaults.get('quiet') | |||
r1138 | log.info("Any existing database is going to be destroyed") | |||
r2651 | if self.tests or quiet: | |||
r1138 | destroy = True | |||
else: | ||||
destroy = ask_ok('Are you sure to destroy old database ? [y/n]') | ||||
if not destroy: | ||||
sys.exit() | ||||
if destroy: | ||||
meta.Base.metadata.drop_all() | ||||
r964 | ||||
r547 | checkfirst = not override | |||
meta.Base.metadata.create_all(checkfirst=checkfirst) | ||||
r1976 | log.info('Created tables for %s' % self.dbname) | |||
r629 | ||||
r834 | def set_db_version(self): | |||
r1734 | ver = DbMigrateVersion() | |||
ver.version = __dbversion__ | ||||
ver.repository_id = 'rhodecode_db_migrations' | ||||
ver.repository_path = 'versions' | ||||
self.sa.add(ver) | ||||
r1976 | log.info('db version set to: %s' % __dbversion__) | |||
r834 | ||||
r839 | def upgrade(self): | |||
r1783 | """ | |||
Upgrades given database schema to given revision following | ||||
r900 | all needed steps, to perform the upgrade | |||
r1203 | ||||
r839 | """ | |||
r841 | ||||
from rhodecode.lib.dbmigrate.migrate.versioning import api | ||||
from rhodecode.lib.dbmigrate.migrate.exceptions import \ | ||||
DatabaseNotControlledError | ||||
r1835 | if 'sqlite' in self.dburi: | |||
print ( | ||||
'********************** WARNING **********************\n' | ||||
'Make sure your version of sqlite is at least 3.7.X. \n' | ||||
'Earlier versions are known to fail on some migrations\n' | ||||
'*****************************************************\n' | ||||
) | ||||
r839 | upgrade = ask_ok('You are about to perform database upgrade, make ' | |||
'sure You backed up your database before. ' | ||||
'Continue ? [y/n]') | ||||
if not upgrade: | ||||
sys.exit('Nothing done') | ||||
r843 | repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))), | |||
'rhodecode/lib/dbmigrate') | ||||
r839 | db_uri = self.dburi | |||
try: | ||||
curr_version = api.db_version(db_uri, repository_path) | ||||
msg = ('Found current database under version' | ||||
' control with version %s' % curr_version) | ||||
r1299 | except (RuntimeError, DatabaseNotControlledError): | |||
r839 | curr_version = 1 | |||
msg = ('Current database is not under version control. Setting' | ||||
' as version %s' % curr_version) | ||||
api.version_control(db_uri, repository_path, curr_version) | ||||
print (msg) | ||||
if curr_version == __dbversion__: | ||||
sys.exit('This database is already at the newest version') | ||||
#====================================================================== | ||||
# UPGRADE STEPS | ||||
#====================================================================== | ||||
class UpgradeSteps(object): | ||||
r1783 | """ | |||
Those steps follow schema versions so for example schema | ||||
r900 | for example schema with seq 002 == step_2 and so on. | |||
""" | ||||
r839 | ||||
def __init__(self, klass): | ||||
self.klass = klass | ||||
def step_0(self): | ||||
r1783 | # step 0 is the schema upgrade, and than follow proper upgrades | |||
r839 | print ('attempting to do database upgrade to version %s' \ | |||
% __dbversion__) | ||||
api.upgrade(db_uri, repository_path, __dbversion__) | ||||
print ('Schema upgrade completed') | ||||
def step_1(self): | ||||
pass | ||||
def step_2(self): | ||||
print ('Patching repo paths for newer version of RhodeCode') | ||||
self.klass.fix_repo_paths() | ||||
print ('Patching default user of RhodeCode') | ||||
self.klass.fix_default_user() | ||||
log.info('Changing ui settings') | ||||
self.klass.create_ui_settings() | ||||
r890 | def step_3(self): | |||
print ('Adding additional settings into RhodeCode db') | ||||
self.klass.fix_settings() | ||||
r1510 | print ('Adding ldap defaults') | |||
self.klass.create_ldap_options(skip_existing=True) | ||||
r1734 | ||||
r1835 | def step_4(self): | |||
r2000 | print ('create permissions and fix groups') | |||
r1987 | self.klass.create_permissions() | |||
r1985 | self.klass.fixup_groups() | |||
r1835 | ||||
r2000 | def step_5(self): | |||
pass | ||||
r2006 | ||||
r2214 | def step_6(self): | |||
pass | ||||
r839 | upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1) | |||
r1783 | # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE | |||
r839 | for step in upgrade_steps: | |||
print ('performing upgrade step %s' % step) | ||||
r1716 | getattr(UpgradeSteps(self), 'step_%s' % step)() | |||
r1987 | self.sa.commit() | |||
r2000 | ||||
r837 | def fix_repo_paths(self): | |||
r1783 | """ | |||
Fixes a old rhodecode version path into new one without a '*' | ||||
r837 | """ | |||
paths = self.sa.query(RhodeCodeUi)\ | ||||
.filter(RhodeCodeUi.ui_key == '/')\ | ||||
.scalar() | ||||
paths.ui_value = paths.ui_value.replace('*', '') | ||||
try: | ||||
self.sa.add(paths) | ||||
self.sa.commit() | ||||
except: | ||||
self.sa.rollback() | ||||
raise | ||||
r838 | def fix_default_user(self): | |||
r1783 | """ | |||
Fixes a old default user with some 'nicer' default values, | ||||
r838 | used mostly for anonymous access | |||
""" | ||||
def_user = self.sa.query(User)\ | ||||
.filter(User.username == 'default')\ | ||||
.one() | ||||
def_user.name = 'Anonymous' | ||||
def_user.lastname = 'User' | ||||
def_user.email = 'anonymous@rhodecode.org' | ||||
try: | ||||
self.sa.add(def_user) | ||||
self.sa.commit() | ||||
except: | ||||
self.sa.rollback() | ||||
raise | ||||
r890 | def fix_settings(self): | |||
r1783 | """ | |||
Fixes rhodecode settings adds ga_code key for google analytics | ||||
r890 | """ | |||
r837 | ||||
r1633 | hgsettings3 = RhodeCodeSetting('ga_code', '') | |||
r907 | ||||
r890 | try: | |||
self.sa.add(hgsettings3) | ||||
self.sa.commit() | ||||
except: | ||||
self.sa.rollback() | ||||
raise | ||||
r837 | ||||
r2284 | def admin_prompt(self, second=False, defaults={}): | |||
r547 | if not self.tests: | |||
import getpass | ||||
r629 | ||||
r2284 | # defaults | |||
username = defaults.get('username') | ||||
password = defaults.get('password') | ||||
email = defaults.get('email') | ||||
r597 | def get_password(): | |||
r1266 | password = getpass.getpass('Specify admin password ' | |||
'(min 6 chars):') | ||||
r597 | confirm = getpass.getpass('Confirm password:') | |||
r629 | ||||
r597 | if password != confirm: | |||
log.error('passwords mismatch') | ||||
return False | ||||
if len(password) < 6: | ||||
log.error('password is to short use at least 6 characters') | ||||
return False | ||||
r629 | ||||
r597 | return password | |||
r2284 | if username is None: | |||
username = raw_input('Specify admin username:') | ||||
if password is None: | ||||
r597 | password = get_password() | |||
if not password: | ||||
r2284 | #second try | |||
password = get_password() | ||||
if not password: | ||||
sys.exit() | ||||
if email is None: | ||||
email = raw_input('Specify admin email:') | ||||
r547 | self.create_user(username, password, email, True) | |||
else: | ||||
log.info('creating admin and regular test users') | ||||
r1758 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\ | |||
r1783 | TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\ | |||
TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\ | ||||
TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \ | ||||
TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL | ||||
r1758 | ||||
self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS, | ||||
TEST_USER_ADMIN_EMAIL, True) | ||||
r1783 | ||||
r1758 | self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, | |||
TEST_USER_REGULAR_EMAIL, False) | ||||
r1783 | ||||
r1758 | self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS, | |||
TEST_USER_REGULAR2_EMAIL, False) | ||||
r629 | ||||
r837 | def create_ui_settings(self): | |||
r1783 | """ | |||
Creates ui settings, fills out hooks | ||||
r837 | and disables dotencode | |||
r1783 | """ | |||
r1203 | ||||
r837 | #HOOKS | |||
r1460 | hooks1_key = RhodeCodeUi.HOOK_UPDATE | |||
r837 | hooks1_ = self.sa.query(RhodeCodeUi)\ | |||
.filter(RhodeCodeUi.ui_key == hooks1_key).scalar() | ||||
r629 | ||||
r837 | hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_ | |||
hooks1.ui_section = 'hooks' | ||||
hooks1.ui_key = hooks1_key | ||||
hooks1.ui_value = 'hg update >&2' | ||||
hooks1.ui_active = False | ||||
r2726 | self.sa.add(hooks1) | |||
r837 | ||||
r1460 | hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE | |||
r837 | hooks2_ = self.sa.query(RhodeCodeUi)\ | |||
.filter(RhodeCodeUi.ui_key == hooks2_key).scalar() | ||||
hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_ | ||||
hooks2.ui_section = 'hooks' | ||||
hooks2.ui_key = hooks2_key | ||||
hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size' | ||||
r2726 | self.sa.add(hooks2) | |||
r837 | ||||
hooks3 = RhodeCodeUi() | ||||
hooks3.ui_section = 'hooks' | ||||
r1460 | hooks3.ui_key = RhodeCodeUi.HOOK_PUSH | |||
r837 | hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action' | |||
r2726 | self.sa.add(hooks3) | |||
r837 | ||||
hooks4 = RhodeCodeUi() | ||||
hooks4.ui_section = 'hooks' | ||||
r2726 | hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH | |||
hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push' | ||||
self.sa.add(hooks4) | ||||
r837 | ||||
r2726 | hooks5 = RhodeCodeUi() | |||
hooks5.ui_section = 'hooks' | ||||
hooks5.ui_key = RhodeCodeUi.HOOK_PULL | ||||
hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action' | ||||
self.sa.add(hooks5) | ||||
hooks6 = RhodeCodeUi() | ||||
hooks6.ui_section = 'hooks' | ||||
hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL | ||||
hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull' | ||||
self.sa.add(hooks6) | ||||
r837 | ||||
r1694 | # enable largefiles | |||
r1734 | largefiles = RhodeCodeUi() | |||
largefiles.ui_section = 'extensions' | ||||
largefiles.ui_key = 'largefiles' | ||||
r1783 | largefiles.ui_value = '' | |||
r2726 | self.sa.add(largefiles) | |||
r1694 | ||||
r2705 | # enable hgsubversion disabled by default | |||
hgsubversion = RhodeCodeUi() | ||||
hgsubversion.ui_section = 'extensions' | ||||
hgsubversion.ui_key = 'hgsubversion' | ||||
hgsubversion.ui_value = '' | ||||
hgsubversion.ui_active = False | ||||
r2726 | self.sa.add(hgsubversion) | |||
r2705 | ||||
r2708 | # enable hggit disabled by default | |||
hggit = RhodeCodeUi() | ||||
hggit.ui_section = 'extensions' | ||||
hggit.ui_key = 'hggit' | ||||
hggit.ui_value = '' | ||||
hggit.ui_active = False | ||||
self.sa.add(hggit) | ||||
r837 | ||||
r1734 | def create_ldap_options(self, skip_existing=False): | |||
r837 | """Creates ldap settings""" | |||
r1734 | for k, v in [('ldap_active', 'false'), ('ldap_host', ''), | |||
('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'), | ||||
('ldap_tls_reqcert', ''), ('ldap_dn_user', ''), | ||||
('ldap_dn_pass', ''), ('ldap_base_dn', ''), | ||||
('ldap_filter', ''), ('ldap_search_scope', ''), | ||||
('ldap_attr_login', ''), ('ldap_attr_firstname', ''), | ||||
('ldap_attr_lastname', ''), ('ldap_attr_email', '')]: | ||||
r837 | ||||
r1734 | if skip_existing and RhodeCodeSetting.get_by_name(k) != None: | |||
log.debug('Skipping option %s' % k) | ||||
continue | ||||
setting = RhodeCodeSetting(k, v) | ||||
self.sa.add(setting) | ||||
r629 | ||||
r1985 | def fixup_groups(self): | |||
def_usr = User.get_by_username('default') | ||||
for g in RepoGroup.query().all(): | ||||
g.group_name = g.get_new_name(g.name) | ||||
self.sa.add(g) | ||||
# get default perm | ||||
default = UserRepoGroupToPerm.query()\ | ||||
.filter(UserRepoGroupToPerm.group == g)\ | ||||
.filter(UserRepoGroupToPerm.user == def_usr)\ | ||||
.scalar() | ||||
if default is None: | ||||
log.debug('missing default permission for group %s adding' % g) | ||||
ReposGroupModel()._create_default_perms(g) | ||||
r2284 | def config_prompt(self, test_repo_path='', retries=3, defaults={}): | |||
_path = defaults.get('repos_location') | ||||
r1094 | if retries == 3: | |||
log.info('Setting up repositories config') | ||||
r629 | ||||
r2284 | if _path is not None: | |||
path = _path | ||||
elif not self.tests and not test_repo_path: | ||||
r1896 | path = raw_input( | |||
r2112 | 'Enter a valid absolute path to store repositories. ' | |||
r1896 | 'All repositories in that path will be added automatically:' | |||
) | ||||
r547 | else: | |||
path = test_repo_path | ||||
r1094 | path_ok = True | |||
r629 | ||||
r1783 | # check proper dir | |||
r547 | if not os.path.isdir(path): | |||
r1094 | path_ok = False | |||
r1976 | log.error('Given path %s is not a valid directory' % path) | |||
r1094 | ||||
r2112 | elif not os.path.isabs(path): | |||
path_ok = False | ||||
log.error('Given path %s is not an absolute path' % path) | ||||
r1783 | # check write access | |||
r2112 | elif not os.access(path, os.W_OK) and path_ok: | |||
r1094 | path_ok = False | |||
r1976 | log.error('No write permission to given path %s' % path) | |||
r1094 | ||||
if retries == 0: | ||||
r1399 | sys.exit('max retries reached') | |||
r1094 | if path_ok is False: | |||
retries -= 1 | ||||
return self.config_prompt(test_repo_path, retries) | ||||
return path | ||||
def create_settings(self, path): | ||||
r629 | ||||
r837 | self.create_ui_settings() | |||
r673 | ||||
r837 | #HG UI OPTIONS | |||
r549 | web1 = RhodeCodeUi() | |||
r547 | web1.ui_section = 'web' | |||
web1.ui_key = 'push_ssl' | ||||
web1.ui_value = 'false' | ||||
r629 | ||||
r549 | web2 = RhodeCodeUi() | |||
r547 | web2.ui_section = 'web' | |||
web2.ui_key = 'allow_archive' | ||||
web2.ui_value = 'gz zip bz2' | ||||
r629 | ||||
r549 | web3 = RhodeCodeUi() | |||
r547 | web3.ui_section = 'web' | |||
web3.ui_key = 'allow_push' | ||||
web3.ui_value = '*' | ||||
r629 | ||||
r549 | web4 = RhodeCodeUi() | |||
r547 | web4.ui_section = 'web' | |||
web4.ui_key = 'baseurl' | ||||
r629 | web4.ui_value = '/' | |||
r549 | paths = RhodeCodeUi() | |||
r547 | paths.ui_section = 'paths' | |||
paths.ui_key = '/' | ||||
r631 | paths.ui_value = path | |||
r629 | ||||
r2726 | phases = RhodeCodeUi() | |||
phases.ui_section = 'phases' | ||||
phases.ui_key = 'publish' | ||||
phases.ui_value = False | ||||
r2662 | sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication') | |||
sett2 = RhodeCodeSetting('title', 'RhodeCode') | ||||
sett3 = RhodeCodeSetting('ga_code', '') | ||||
sett4 = RhodeCodeSetting('show_public_icon', True) | ||||
sett5 = RhodeCodeSetting('show_private_icon', True) | ||||
sett6 = RhodeCodeSetting('stylify_metatags', False) | ||||
r629 | ||||
r1734 | self.sa.add(web1) | |||
self.sa.add(web2) | ||||
self.sa.add(web3) | ||||
self.sa.add(web4) | ||||
self.sa.add(paths) | ||||
r2662 | self.sa.add(sett1) | |||
self.sa.add(sett2) | ||||
self.sa.add(sett3) | ||||
self.sa.add(sett4) | ||||
self.sa.add(sett5) | ||||
self.sa.add(sett6) | ||||
r837 | ||||
self.create_ldap_options() | ||||
r547 | log.info('created ui config') | |||
r629 | ||||
r547 | def create_user(self, username, password, email='', admin=False): | |||
r1976 | log.info('creating user %s' % username) | |||
r1734 | UserModel().create_or_update(username, password, email, | |||
r2513 | firstname='RhodeCode', lastname='Admin', | |||
r1634 | active=True, admin=admin) | |||
r547 | ||||
def create_default_user(self): | ||||
log.info('creating default user') | ||||
r1634 | # create default user for handling default permissions. | |||
r1734 | UserModel().create_or_update(username='default', | |||
password=str(uuid.uuid1())[:8], | ||||
email='anonymous@rhodecode.org', | ||||
r2513 | firstname='Anonymous', lastname='User') | |||
r1734 | ||||
r547 | def create_permissions(self): | |||
r1783 | # module.(access|create|change|delete)_[name] | |||
r1982 | # module.(none|read|write|admin) | |||
r1266 | ||||
r2532 | for p in Permission.PERMS: | |||
r1987 | if not Permission.get_by_key(p[0]): | |||
r1986 | new_perm = Permission() | |||
new_perm.permission_name = p[0] | ||||
r2532 | new_perm.permission_longname = p[0] | |||
r1986 | self.sa.add(new_perm) | |||
r547 | ||||
def populate_default_permissions(self): | ||||
log.info('creating default user permissions') | ||||
r629 | ||||
r2709 | default_user = User.get_by_username('default') | |||
r629 | ||||
r547 | reg_perm = UserToPerm() | |||
reg_perm.user = default_user | ||||
reg_perm.permission = self.sa.query(Permission)\ | ||||
r2709 | .filter(Permission.permission_name == 'hg.register.manual_activate')\ | |||
.scalar() | ||||
self.sa.add(reg_perm) | ||||
r629 | ||||
r547 | create_repo_perm = UserToPerm() | |||
create_repo_perm.user = default_user | ||||
create_repo_perm.permission = self.sa.query(Permission)\ | ||||
r2709 | .filter(Permission.permission_name == 'hg.create.repository')\ | |||
.scalar() | ||||
self.sa.add(create_repo_perm) | ||||
default_fork_perm = UserToPerm() | ||||
default_fork_perm.user = default_user | ||||
default_fork_perm.permission = self.sa.query(Permission)\ | ||||
.filter(Permission.permission_name == 'hg.fork.repository')\ | ||||
.scalar() | ||||
self.sa.add(default_fork_perm) | ||||
r629 | ||||
r547 | default_repo_perm = UserToPerm() | |||
default_repo_perm.user = default_user | ||||
default_repo_perm.permission = self.sa.query(Permission)\ | ||||
r2709 | .filter(Permission.permission_name == 'repository.read')\ | |||
.scalar() | ||||
r1734 | self.sa.add(default_repo_perm) | |||