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