##// END OF EJS Templates
fixed wrong migration schema...
marcink -
r837:60cbde08 beta
parent child Browse files
Show More
@@ -1,332 +1,382 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode
7 7
8 8 :created_on: Apr 10, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import os
29 29 import sys
30 30 import uuid
31 31 import logging
32 32 from os.path import dirname as dn, join as jn
33 33
34 34 from rhodecode import __dbversion__
35 from rhodecode.model.db import
36 35 from rhodecode.model import meta
37 36
38 37 from rhodecode.lib.auth import get_crypt_password
39 38 from rhodecode.lib.utils import ask_ok
40 39 from rhodecode.model import init_model
41 40 from rhodecode.model.db import User, Permission, RhodeCodeUi, RhodeCodeSettings, \
42 41 UserToPerm, DbMigrateVersion
43 42
44 43 from sqlalchemy.engine import create_engine
45 44
46 45
47 46 log = logging.getLogger(__name__)
48 47
49 48 class DbManage(object):
50 49 def __init__(self, log_sql, dbconf, root, tests=False):
51 50 self.dbname = dbconf.split('/')[-1]
52 51 self.tests = tests
53 52 self.root = root
54 53 self.dburi = dbconf
55 54 engine = create_engine(self.dburi, echo=log_sql)
56 55 init_model(engine)
57 56 self.sa = meta.Session()
58 57 self.db_exists = False
59 58
60 59 def check_for_db(self, override):
61 60 db_path = jn(self.root, self.dbname)
62 61 if self.dburi.startswith('sqlite'):
63 62 log.info('checking for existing db in %s', db_path)
64 63 if os.path.isfile(db_path):
65 64
66 65 self.db_exists = True
67 66 if not override:
68 67 raise Exception('database already exists')
69 68
70 69 def create_tables(self, override=False):
70 """Create a auth database
71 71 """
72 Create a auth database
73 """
72
74 73 self.check_for_db(override)
75 74 if self.db_exists:
76 75 log.info("database exist and it's going to be destroyed")
77 76 if self.tests:
78 77 destroy = True
79 78 else:
80 79 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
81 80 if not destroy:
82 81 sys.exit()
83 82 if self.db_exists and destroy:
84 83 os.remove(jn(self.root, self.dbname))
85 84 checkfirst = not override
86 85 meta.Base.metadata.create_all(checkfirst=checkfirst)
87 86 log.info('Created tables for %s', self.dbname)
88 87
89 88
90 89
91 90 def set_db_version(self):
92 91 try:
93 92 ver = DbMigrateVersion()
94 93 ver.version = __dbversion__
95 94 ver.repository_id = 'rhodecode_db_migrations'
96 95 ver.repository_path = 'versions'
97 96 self.sa.add(ver)
98 97 self.sa.commit()
99 98 except:
100 99 self.sa.rollback()
101 100 raise
102 101 log.info('db version set to: %s', __dbversion__)
103 102
103 def fix_repo_paths(self):
104 """Fixes a old rhodecode version path into new one without a '*'
105 """
106
107 paths = self.sa.query(RhodeCodeUi)\
108 .filter(RhodeCodeUi.ui_key == '/')\
109 .scalar()
110
111 paths.ui_value = paths.ui_value.replace('*', '')
112
113 try:
114 self.sa.add(paths)
115 self.sa.commit()
116 except:
117 self.sa.rollback()
118 raise
119
120
121
104 122 def admin_prompt(self, second=False):
105 123 if not self.tests:
106 124 import getpass
107 125
108 126
109 127 def get_password():
110 128 password = getpass.getpass('Specify admin password (min 6 chars):')
111 129 confirm = getpass.getpass('Confirm password:')
112 130
113 131 if password != confirm:
114 132 log.error('passwords mismatch')
115 133 return False
116 134 if len(password) < 6:
117 135 log.error('password is to short use at least 6 characters')
118 136 return False
119 137
120 138 return password
121 139
122 140 username = raw_input('Specify admin username:')
123 141
124 142 password = get_password()
125 143 if not password:
126 144 #second try
127 145 password = get_password()
128 146 if not password:
129 147 sys.exit()
130 148
131 149 email = raw_input('Specify admin email:')
132 150 self.create_user(username, password, email, True)
133 151 else:
134 152 log.info('creating admin and regular test users')
135 153 self.create_user('test_admin', 'test12', 'test_admin@mail.com', True)
136 154 self.create_user('test_regular', 'test12', 'test_regular@mail.com', False)
137 155 self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False)
138 156
157 def create_ui_settings(self):
158 """Creates ui settings, fills out hooks
159 and disables dotencode
160
161 """
162 #HOOKS
163 hooks1_key = 'changegroup.update'
164 hooks1_ = self.sa.query(RhodeCodeUi)\
165 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
139 166
167 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
168 hooks1.ui_section = 'hooks'
169 hooks1.ui_key = hooks1_key
170 hooks1.ui_value = 'hg update >&2'
171 hooks1.ui_active = False
172
173 hooks2_key = 'changegroup.repo_size'
174 hooks2_ = self.sa.query(RhodeCodeUi)\
175 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
176
177 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
178 hooks2.ui_section = 'hooks'
179 hooks2.ui_key = hooks2_key
180 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
181
182 hooks3 = RhodeCodeUi()
183 hooks3.ui_section = 'hooks'
184 hooks3.ui_key = 'pretxnchangegroup.push_logger'
185 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
186
187 hooks4 = RhodeCodeUi()
188 hooks4.ui_section = 'hooks'
189 hooks4.ui_key = 'preoutgoing.pull_logger'
190 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
191
192 #For mercurial 1.7 set backward comapatibility with format
193 dotencode_disable = RhodeCodeUi()
194 dotencode_disable.ui_section = 'format'
195 dotencode_disable.ui_key = 'dotencode'
196 dotencode_disable.ui_value = 'false'
197
198 try:
199 self.sa.add(hooks1)
200 self.sa.add(hooks2)
201 self.sa.add(hooks3)
202 self.sa.add(hooks4)
203 self.sa.add(dotencode_disable)
204 self.sa.commit()
205 except:
206 self.sa.rollback()
207 raise
208
209
210 def create_ldap_options(self):
211 """Creates ldap settings"""
212
213 try:
214 for k in ['ldap_active', 'ldap_host', 'ldap_port', 'ldap_ldaps',
215 'ldap_dn_user', 'ldap_dn_pass', 'ldap_base_dn']:
216
217 setting = RhodeCodeSettings(k, '')
218 self.sa.add(setting)
219 self.sa.commit()
220 except:
221 self.sa.rollback()
222 raise
140 223
141 224 def config_prompt(self, test_repo_path=''):
142 225 log.info('Setting up repositories config')
143 226
144 227 if not self.tests and not test_repo_path:
145 228 path = raw_input('Specify valid full path to your repositories'
146 229 ' you can change this later in application settings:')
147 230 else:
148 231 path = test_repo_path
149 232
150 233 if not os.path.isdir(path):
151 234 log.error('You entered wrong path: %s', path)
152 235 sys.exit()
153 236
154 hooks1 = RhodeCodeUi()
155 hooks1.ui_section = 'hooks'
156 hooks1.ui_key = 'changegroup.update'
157 hooks1.ui_value = 'hg update >&2'
158 hooks1.ui_active = False
159
160 hooks2 = RhodeCodeUi()
161 hooks2.ui_section = 'hooks'
162 hooks2.ui_key = 'changegroup.repo_size'
163 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
237 self.create_ui_settings()
164 238
165 hooks3 = RhodeCodeUi()
166 hooks3.ui_section = 'hooks'
167 hooks3.ui_key = 'pretxnchangegroup.push_logger'
168 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
169
170 hooks4 = RhodeCodeUi()
171 hooks4.ui_section = 'hooks'
172 hooks4.ui_key = 'preoutgoing.pull_logger'
173 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
174
175 #for mercurial 1.7 set backward comapatibility with format
176
177 dotencode_disable = RhodeCodeUi()
178 dotencode_disable.ui_section = 'format'
179 dotencode_disable.ui_key = 'dotencode'
180 dotencode_disable.ui_section = 'false'
181
182
239 #HG UI OPTIONS
183 240 web1 = RhodeCodeUi()
184 241 web1.ui_section = 'web'
185 242 web1.ui_key = 'push_ssl'
186 243 web1.ui_value = 'false'
187 244
188 245 web2 = RhodeCodeUi()
189 246 web2.ui_section = 'web'
190 247 web2.ui_key = 'allow_archive'
191 248 web2.ui_value = 'gz zip bz2'
192 249
193 250 web3 = RhodeCodeUi()
194 251 web3.ui_section = 'web'
195 252 web3.ui_key = 'allow_push'
196 253 web3.ui_value = '*'
197 254
198 255 web4 = RhodeCodeUi()
199 256 web4.ui_section = 'web'
200 257 web4.ui_key = 'baseurl'
201 258 web4.ui_value = '/'
202 259
203 260 paths = RhodeCodeUi()
204 261 paths.ui_section = 'paths'
205 262 paths.ui_key = '/'
206 263 paths.ui_value = path
207 264
208 265
209 266 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
210 267 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
211 268
212 269
213 270 try:
214 self.sa.add(hooks1)
215 self.sa.add(hooks2)
216 self.sa.add(hooks3)
217 self.sa.add(hooks4)
218 271 self.sa.add(web1)
219 272 self.sa.add(web2)
220 273 self.sa.add(web3)
221 274 self.sa.add(web4)
222 275 self.sa.add(paths)
223 276 self.sa.add(hgsettings1)
224 277 self.sa.add(hgsettings2)
225 self.sa.add(dotencode_disable)
226 for k in ['ldap_active', 'ldap_host', 'ldap_port', 'ldap_ldaps',
227 'ldap_dn_user', 'ldap_dn_pass', 'ldap_base_dn']:
228
229 setting = RhodeCodeSettings(k, '')
230 self.sa.add(setting)
231 278
232 279 self.sa.commit()
233 280 except:
234 281 self.sa.rollback()
235 282 raise
283
284 self.create_ldap_options()
285
236 286 log.info('created ui config')
237 287
238 288 def create_user(self, username, password, email='', admin=False):
239 289 log.info('creating administrator user %s', username)
240 290 new_user = User()
241 291 new_user.username = username
242 292 new_user.password = get_crypt_password(password)
243 293 new_user.name = 'RhodeCode'
244 294 new_user.lastname = 'Admin'
245 295 new_user.email = email
246 296 new_user.admin = admin
247 297 new_user.active = True
248 298
249 299 try:
250 300 self.sa.add(new_user)
251 301 self.sa.commit()
252 302 except:
253 303 self.sa.rollback()
254 304 raise
255 305
256 306 def create_default_user(self):
257 307 log.info('creating default user')
258 308 #create default user for handling default permissions.
259 309 def_user = User()
260 310 def_user.username = 'default'
261 311 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
262 312 def_user.name = 'Anonymous'
263 313 def_user.lastname = 'User'
264 314 def_user.email = 'anonymous@rhodecode.org'
265 315 def_user.admin = False
266 316 def_user.active = False
267 317 try:
268 318 self.sa.add(def_user)
269 319 self.sa.commit()
270 320 except:
271 321 self.sa.rollback()
272 322 raise
273 323
274 324 def create_permissions(self):
275 325 #module.(access|create|change|delete)_[name]
276 326 #module.(read|write|owner)
277 327 perms = [('repository.none', 'Repository no access'),
278 328 ('repository.read', 'Repository read access'),
279 329 ('repository.write', 'Repository write access'),
280 330 ('repository.admin', 'Repository admin access'),
281 331 ('hg.admin', 'Hg Administrator'),
282 332 ('hg.create.repository', 'Repository create'),
283 333 ('hg.create.none', 'Repository creation disabled'),
284 334 ('hg.register.none', 'Register disabled'),
285 335 ('hg.register.manual_activate', 'Register new user with rhodecode without manual activation'),
286 336 ('hg.register.auto_activate', 'Register new user with rhodecode without auto activation'),
287 337 ]
288 338
289 339 for p in perms:
290 340 new_perm = Permission()
291 341 new_perm.permission_name = p[0]
292 342 new_perm.permission_longname = p[1]
293 343 try:
294 344 self.sa.add(new_perm)
295 345 self.sa.commit()
296 346 except:
297 347 self.sa.rollback()
298 348 raise
299 349
300 350 def populate_default_permissions(self):
301 351 log.info('creating default user permissions')
302 352
303 353 default_user = self.sa.query(User)\
304 354 .filter(User.username == 'default').scalar()
305 355
306 356 reg_perm = UserToPerm()
307 357 reg_perm.user = default_user
308 358 reg_perm.permission = self.sa.query(Permission)\
309 359 .filter(Permission.permission_name == 'hg.register.manual_activate')\
310 360 .scalar()
311 361
312 362 create_repo_perm = UserToPerm()
313 363 create_repo_perm.user = default_user
314 364 create_repo_perm.permission = self.sa.query(Permission)\
315 365 .filter(Permission.permission_name == 'hg.create.repository')\
316 366 .scalar()
317 367
318 368 default_repo_perm = UserToPerm()
319 369 default_repo_perm.user = default_user
320 370 default_repo_perm.permission = self.sa.query(Permission)\
321 371 .filter(Permission.permission_name == 'repository.read')\
322 372 .scalar()
323 373
324 374 try:
325 375 self.sa.add(reg_perm)
326 376 self.sa.add(create_repo_perm)
327 377 self.sa.add(default_repo_perm)
328 378 self.sa.commit()
329 379 except:
330 380 self.sa.rollback()
331 381 raise
332 382
@@ -1,86 +1,101 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.dbmigrate.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database migration modules
7 7
8 8 :created_on: Dec 11, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import logging
29 29 from sqlalchemy import engine_from_config
30 30
31 31 from rhodecode import __dbversion__
32 32 from rhodecode.lib.dbmigrate.migrate.versioning import api
33 33 from rhodecode.lib.dbmigrate.migrate.exceptions import \
34 34 DatabaseNotControlledError
35 35 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
36 from rhodecode.lib.db_manage import DbManage
36 37
37 38 log = logging.getLogger(__name__)
38 39
39 40 class UpgradeDb(BasePasterCommand):
40 41 """Command used for paster to upgrade our database to newer version
41 42 """
42 43
43 44 max_args = 1
44 45 min_args = 1
45 46
46 47 usage = "CONFIG_FILE"
47 48 summary = "Upgrades current db to newer version given configuration file"
48 49 group_name = "RhodeCode"
49 50
50 51 parser = Command.standard_parser(verbose=True)
51 52
52 53 def command(self):
53 54 from pylons import config
54 55
55 56 add_cache(config)
56 57
57 #engine = engine_from_config(config, 'sqlalchemy.db1.')
58
58 59
59 60 repository_path = 'rhodecode/lib/dbmigrate'
60 61 db_uri = config['sqlalchemy.db1.url']
61 62
62 63 try:
63 64 curr_version = api.db_version(db_uri, repository_path)
64 65 msg = ('Found current database under version'
65 66 ' control with version %s' % curr_version)
66 67
67 68 except (RuntimeError, DatabaseNotControlledError), e:
68 69 curr_version = 1
69 70 msg = ('Current database is not under version control setting'
70 71 ' as version %s' % curr_version)
71 72 api.version_control(db_uri, repository_path, curr_version)
72 73
74 self.notify_msg(msg)
73 75
74 print msg
75 76 #now we have our dbversion we can do upgrade
77 self.notify_msg('attempting to do database upgrade to version %s' \
78 % __dbversion__)
79
80 api.upgrade(db_uri, repository_path, __dbversion__)
81 self.notify_msg('Schema upgrade completed')
76 82
77 msg = 'attempting to do database upgrade to version %s' % __dbversion__
78 print msg
79 api.upgrade(db_uri, repository_path, __dbversion__)
83 #we need to make now some extra operations into database
84 self.notify_msg('Prociding with database updates')
85
86 dbmanage = DbManage(log_sql=True, dbconf=db_uri,
87 root=config['here'], tests=False)
88
89 self.notify_msg('Patching repo paths for newer version of rhodecode')
90 dbmanage.fix_repo_paths()
91
92 self.notify_msg('Changing ui settings')
93 dbmanage.create_ui_settings()
94
80 95
81 96 def update_parser(self):
82 97 self.parser.add_option('--sql',
83 98 action='store_true',
84 99 dest='just_sql',
85 100 help="Prints upgrade sql for further investigation",
86 101 default=False)
@@ -1,129 +1,127 b''
1 1 import logging
2 2 import datetime
3 3
4 4 from sqlalchemy import *
5 5 from sqlalchemy.exc import DatabaseError
6 6 from sqlalchemy.orm import relation, backref, class_mapper
7 7 from sqlalchemy.orm.session import Session
8 8 from rhodecode.model.meta import Base
9 9 from rhodecode.model.db import BaseModel
10 10
11 11 from rhodecode.lib.dbmigrate.migrate import *
12 12
13 13 log = logging.getLogger(__name__)
14 14
15 15 def upgrade(migrate_engine):
16 16 """ Upgrade operations go here.
17 17 Don't create your own engine; bind migrate_engine to your metadata
18 18 """
19 19
20 20 #==========================================================================
21 21 # Upgrade of `users` table
22 22 #==========================================================================
23 23 tblname = 'users'
24 24 tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True,
25 25 autoload_with=migrate_engine)
26 26
27 27 #ADD is_ldap column
28 28 is_ldap = Column("is_ldap", Boolean(), nullable=True,
29 29 unique=None, default=False)
30 30 is_ldap.create(tbl, populate_default=True)
31 31 is_ldap.alter(nullable=False)
32 32
33 33 #==========================================================================
34 34 # Upgrade of `user_logs` table
35 35 #==========================================================================
36 36
37 37 tblname = 'users'
38 38 tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True,
39 39 autoload_with=migrate_engine)
40 40
41 41 #ADD revision column
42 42 revision = Column('revision', TEXT(length=None, convert_unicode=False,
43 43 assert_unicode=None),
44 44 nullable=True, unique=None, default=None)
45 45 revision.create(tbl)
46 46
47 47
48 48
49 49 #==========================================================================
50 50 # Upgrade of `repositories` table
51 51 #==========================================================================
52 tblname = 'users'
52 tblname = 'repositories'
53 53 tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True,
54 54 autoload_with=migrate_engine)
55 55
56 #ADD repo_type column
56 #ADD repo_type column#
57 57 repo_type = Column("repo_type", String(length=None, convert_unicode=False,
58 58 assert_unicode=None),
59 59 nullable=True, unique=False, default='hg')
60 60
61 61 repo_type.create(tbl, populate_default=True)
62 repo_type.alter(nullable=False)
62 #repo_type.alter(nullable=False)
63 63
64 #ADD statistics column
64 #ADD statistics column#
65 65 enable_statistics = Column("statistics", Boolean(), nullable=True,
66 66 unique=None, default=True)
67 67 enable_statistics.create(tbl)
68 68
69
70
71 69 #==========================================================================
72 70 # Add table `user_followings`
73 71 #==========================================================================
74 72 tblname = 'user_followings'
75 73
76 74 class UserFollowing(Base, BaseModel):
77 75 __tablename__ = 'user_followings'
78 76 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
79 77 UniqueConstraint('user_id', 'follows_user_id')
80 78 , {'useexisting':True})
81 79
82 80 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
83 81 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
84 82 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
85 83 follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
86 84
87 85 user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
88 86
89 87 follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
90 88 follows_repository = relation('Repository')
91 89
92 90 Base.metadata.tables[tblname].create(migrate_engine)
93 91
94 92 #==========================================================================
95 93 # Add table `cache_invalidation`
96 94 #==========================================================================
97 95 tblname = 'cache_invalidation'
98 96
99 97 class CacheInvalidation(Base, BaseModel):
100 98 __tablename__ = 'cache_invalidation'
101 99 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
102 100 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
103 101 cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
104 102 cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
105 103 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
106 104
107 105
108 106 def __init__(self, cache_key, cache_args=''):
109 107 self.cache_key = cache_key
110 108 self.cache_args = cache_args
111 109 self.cache_active = False
112 110
113 111 def __repr__(self):
114 112 return "<CacheInvalidation('%s:%s')>" % (self.cache_id, self.cache_key)
115 113
116 114 Base.metadata.tables[tblname].create(migrate_engine)
117 115
118 116 return
119 117
120 118
121 119
122 120
123 121
124 122
125 123 def downgrade(migrate_engine):
126 124 meta = MetaData()
127 125 meta.bind = migrate_engine
128 126
129 127
@@ -1,601 +1,614 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import os
29 29 import logging
30 30 import datetime
31 31 import traceback
32 32
33 33 from UserDict import DictMixin
34 34
35 35 from mercurial import ui, config, hg
36 36 from mercurial.error import RepoError
37 37
38 38 import paste
39 39 import beaker
40 40 from paste.script.command import Command, BadCommand
41 41
42 42 from vcs.backends.base import BaseChangeset
43 43 from vcs.utils.lazy import LazyProperty
44 44
45 45 from rhodecode.model import meta
46 46 from rhodecode.model.caching_query import FromCache
47 47 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog
48 48 from rhodecode.model.repo import RepoModel
49 49 from rhodecode.model.user import UserModel
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 def get_repo_slug(request):
55 55 return request.environ['pylons.routes_dict'].get('repo_name')
56 56
57 57 def action_logger(user, action, repo, ipaddr='', sa=None):
58 58 """
59 59 Action logger for various actions made by users
60 60
61 61 :param user: user that made this action, can be a unique username string or
62 62 object containing user_id attribute
63 63 :param action: action to log, should be on of predefined unique actions for
64 64 easy translations
65 65 :param repo: string name of repository or object containing repo_id,
66 66 that action was made on
67 67 :param ipaddr: optional ip address from what the action was made
68 68 :param sa: optional sqlalchemy session
69 69
70 70 """
71 71
72 72 if not sa:
73 73 sa = meta.Session()
74 74
75 75 try:
76 76 um = UserModel()
77 77 if hasattr(user, 'user_id'):
78 78 user_obj = user
79 79 elif isinstance(user, basestring):
80 80 user_obj = um.get_by_username(user, cache=False)
81 81 else:
82 82 raise Exception('You have to provide user object or username')
83 83
84 84
85 85 rm = RepoModel()
86 86 if hasattr(repo, 'repo_id'):
87 87 repo_obj = rm.get(repo.repo_id, cache=False)
88 88 repo_name = repo_obj.repo_name
89 89 elif isinstance(repo, basestring):
90 90 repo_name = repo.lstrip('/')
91 91 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
92 92 else:
93 93 raise Exception('You have to provide repository to action logger')
94 94
95 95
96 96 user_log = UserLog()
97 97 user_log.user_id = user_obj.user_id
98 98 user_log.action = action
99 99
100 100 user_log.repository_id = repo_obj.repo_id
101 101 user_log.repository_name = repo_name
102 102
103 103 user_log.action_date = datetime.datetime.now()
104 104 user_log.user_ip = ipaddr
105 105 sa.add(user_log)
106 106 sa.commit()
107 107
108 108 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
109 109 except:
110 110 log.error(traceback.format_exc())
111 111 sa.rollback()
112 112
113 113 def get_repos(path, recursive=False, initial=False):
114 114 """
115 115 Scans given path for repos and return (name,(type,path)) tuple
116 116 :param prefix:
117 117 :param path:
118 118 :param recursive:
119 119 :param initial:
120 120 """
121 121 from vcs.utils.helpers import get_scm
122 122 from vcs.exceptions import VCSError
123 123
124 124 try:
125 125 scm = get_scm(path)
126 126 except:
127 127 pass
128 128 else:
129 129 raise Exception('The given path %s should not be a repository got %s',
130 130 path, scm)
131 131
132 132 for dirpath in os.listdir(path):
133 133 try:
134 134 yield dirpath, get_scm(os.path.join(path, dirpath))
135 135 except VCSError:
136 136 pass
137 137
138 138 def check_repo_fast(repo_name, base_path):
139 139 """
140 140 Check given path for existance of directory
141 141 :param repo_name:
142 142 :param base_path:
143 143
144 144 :return False: if this directory is present
145 145 """
146 146 if os.path.isdir(os.path.join(base_path, repo_name)):return False
147 147 return True
148 148
149 149 def check_repo(repo_name, base_path, verify=True):
150 150
151 151 repo_path = os.path.join(base_path, repo_name)
152 152
153 153 try:
154 154 if not check_repo_fast(repo_name, base_path):
155 155 return False
156 156 r = hg.repository(ui.ui(), repo_path)
157 157 if verify:
158 158 hg.verify(r)
159 159 #here we hnow that repo exists it was verified
160 160 log.info('%s repo is already created', repo_name)
161 161 return False
162 162 except RepoError:
163 163 #it means that there is no valid repo there...
164 164 log.info('%s repo is free for creation', repo_name)
165 165 return True
166 166
167 167 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
168 168 while True:
169 169 ok = raw_input(prompt)
170 170 if ok in ('y', 'ye', 'yes'): return True
171 171 if ok in ('n', 'no', 'nop', 'nope'): return False
172 172 retries = retries - 1
173 173 if retries < 0: raise IOError
174 174 print complaint
175 175
176 176 #propagated from mercurial documentation
177 177 ui_sections = ['alias', 'auth',
178 178 'decode/encode', 'defaults',
179 179 'diff', 'email',
180 180 'extensions', 'format',
181 181 'merge-patterns', 'merge-tools',
182 182 'hooks', 'http_proxy',
183 183 'smtp', 'patch',
184 184 'paths', 'profiling',
185 185 'server', 'trusted',
186 186 'ui', 'web', ]
187 187
188 188 def make_ui(read_from='file', path=None, checkpaths=True):
189 189 """
190 190 A function that will read python rc files or database
191 191 and make an mercurial ui object from read options
192 192
193 193 :param path: path to mercurial config file
194 194 :param checkpaths: check the path
195 195 :param read_from: read from 'file' or 'db'
196 196 """
197 197
198 198 baseui = ui.ui()
199 199
200 200 #clean the baseui object
201 201 baseui._ocfg = config.config()
202 202 baseui._ucfg = config.config()
203 203 baseui._tcfg = config.config()
204 204
205 205 if read_from == 'file':
206 206 if not os.path.isfile(path):
207 207 log.warning('Unable to read config file %s' % path)
208 208 return False
209 209 log.debug('reading hgrc from %s', path)
210 210 cfg = config.config()
211 211 cfg.read(path)
212 212 for section in ui_sections:
213 213 for k, v in cfg.items(section):
214 214 log.debug('settings ui from file[%s]%s:%s', section, k, v)
215 215 baseui.setconfig(section, k, v)
216 216
217 217
218 218 elif read_from == 'db':
219 219 sa = meta.Session()
220 220 ret = sa.query(RhodeCodeUi)\
221 221 .options(FromCache("sql_cache_short",
222 222 "get_hg_ui_settings")).all()
223 223
224 224 hg_ui = ret
225 225 for ui_ in hg_ui:
226 226 if ui_.ui_active:
227 227 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
228 228 ui_.ui_key, ui_.ui_value)
229 229 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
230 230
231 231 meta.Session.remove()
232 232 return baseui
233 233
234 234
235 235 def set_rhodecode_config(config):
236 236 """
237 237 Updates pylons config with new settings from database
238 238 :param config:
239 239 """
240 240 from rhodecode.model.settings import SettingsModel
241 241 hgsettings = SettingsModel().get_app_settings()
242 242
243 243 for k, v in hgsettings.items():
244 244 config[k] = v
245 245
246 246 def invalidate_cache(cache_key, *args):
247 247 """
248 248 Puts cache invalidation task into db for
249 249 further global cache invalidation
250 250 """
251 251 from rhodecode.model.scm import ScmModel
252 252
253 253 if cache_key.startswith('get_repo_cached_'):
254 254 name = cache_key.split('get_repo_cached_')[-1]
255 255 ScmModel().mark_for_invalidation(name)
256 256
257 257 class EmptyChangeset(BaseChangeset):
258 258 """
259 259 An dummy empty changeset. It's possible to pass hash when creating
260 260 an EmptyChangeset
261 261 """
262 262
263 263 def __init__(self, cs='0' * 40):
264 264 self._empty_cs = cs
265 265 self.revision = -1
266 266 self.message = ''
267 267 self.author = ''
268 268 self.date = ''
269 269
270 270 @LazyProperty
271 271 def raw_id(self):
272 272 """
273 273 Returns raw string identifying this changeset, useful for web
274 274 representation.
275 275 """
276 276 return self._empty_cs
277 277
278 278 @LazyProperty
279 279 def short_id(self):
280 280 return self.raw_id[:12]
281 281
282 282 def get_file_changeset(self, path):
283 283 return self
284 284
285 285 def get_file_content(self, path):
286 286 return u''
287 287
288 288 def get_file_size(self, path):
289 289 return 0
290 290
291 291 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
292 292 """
293 293 maps all found repositories into db
294 294 """
295 295
296 296 sa = meta.Session()
297 297 rm = RepoModel()
298 298 user = sa.query(User).filter(User.admin == True).first()
299 299
300 300 for name, repo in initial_repo_list.items():
301 301 if not rm.get_by_repo_name(name, cache=False):
302 302 log.info('repository %s not found creating default', name)
303 303
304 304 form_data = {
305 305 'repo_name':name,
306 306 'repo_type':repo.alias,
307 307 'description':repo.description \
308 308 if repo.description != 'unknown' else \
309 309 '%s repository' % name,
310 310 'private':False
311 311 }
312 312 rm.create(form_data, user, just_db=True)
313 313
314 314 if remove_obsolete:
315 315 #remove from database those repositories that are not in the filesystem
316 316 for repo in sa.query(Repository).all():
317 317 if repo.repo_name not in initial_repo_list.keys():
318 318 sa.delete(repo)
319 319 sa.commit()
320 320
321 321 class OrderedDict(dict, DictMixin):
322 322
323 323 def __init__(self, *args, **kwds):
324 324 if len(args) > 1:
325 325 raise TypeError('expected at most 1 arguments, got %d' % len(args))
326 326 try:
327 327 self.__end
328 328 except AttributeError:
329 329 self.clear()
330 330 self.update(*args, **kwds)
331 331
332 332 def clear(self):
333 333 self.__end = end = []
334 334 end += [None, end, end] # sentinel node for doubly linked list
335 335 self.__map = {} # key --> [key, prev, next]
336 336 dict.clear(self)
337 337
338 338 def __setitem__(self, key, value):
339 339 if key not in self:
340 340 end = self.__end
341 341 curr = end[1]
342 342 curr[2] = end[1] = self.__map[key] = [key, curr, end]
343 343 dict.__setitem__(self, key, value)
344 344
345 345 def __delitem__(self, key):
346 346 dict.__delitem__(self, key)
347 347 key, prev, next = self.__map.pop(key)
348 348 prev[2] = next
349 349 next[1] = prev
350 350
351 351 def __iter__(self):
352 352 end = self.__end
353 353 curr = end[2]
354 354 while curr is not end:
355 355 yield curr[0]
356 356 curr = curr[2]
357 357
358 358 def __reversed__(self):
359 359 end = self.__end
360 360 curr = end[1]
361 361 while curr is not end:
362 362 yield curr[0]
363 363 curr = curr[1]
364 364
365 365 def popitem(self, last=True):
366 366 if not self:
367 367 raise KeyError('dictionary is empty')
368 368 if last:
369 369 key = reversed(self).next()
370 370 else:
371 371 key = iter(self).next()
372 372 value = self.pop(key)
373 373 return key, value
374 374
375 375 def __reduce__(self):
376 376 items = [[k, self[k]] for k in self]
377 377 tmp = self.__map, self.__end
378 378 del self.__map, self.__end
379 379 inst_dict = vars(self).copy()
380 380 self.__map, self.__end = tmp
381 381 if inst_dict:
382 382 return (self.__class__, (items,), inst_dict)
383 383 return self.__class__, (items,)
384 384
385 385 def keys(self):
386 386 return list(self)
387 387
388 388 setdefault = DictMixin.setdefault
389 389 update = DictMixin.update
390 390 pop = DictMixin.pop
391 391 values = DictMixin.values
392 392 items = DictMixin.items
393 393 iterkeys = DictMixin.iterkeys
394 394 itervalues = DictMixin.itervalues
395 395 iteritems = DictMixin.iteritems
396 396
397 397 def __repr__(self):
398 398 if not self:
399 399 return '%s()' % (self.__class__.__name__,)
400 400 return '%s(%r)' % (self.__class__.__name__, self.items())
401 401
402 402 def copy(self):
403 403 return self.__class__(self)
404 404
405 405 @classmethod
406 406 def fromkeys(cls, iterable, value=None):
407 407 d = cls()
408 408 for key in iterable:
409 409 d[key] = value
410 410 return d
411 411
412 412 def __eq__(self, other):
413 413 if isinstance(other, OrderedDict):
414 414 return len(self) == len(other) and self.items() == other.items()
415 415 return dict.__eq__(self, other)
416 416
417 417 def __ne__(self, other):
418 418 return not self == other
419 419
420 420
421 421 #set cache regions for beaker so celery can utilise it
422 422 def add_cache(settings):
423 423 cache_settings = {'regions':None}
424 424 for key in settings.keys():
425 425 for prefix in ['beaker.cache.', 'cache.']:
426 426 if key.startswith(prefix):
427 427 name = key.split(prefix)[1].strip()
428 428 cache_settings[name] = settings[key].strip()
429 429 if cache_settings['regions']:
430 430 for region in cache_settings['regions'].split(','):
431 431 region = region.strip()
432 432 region_settings = {}
433 433 for key, value in cache_settings.items():
434 434 if key.startswith(region):
435 435 region_settings[key.split('.')[1]] = value
436 436 region_settings['expire'] = int(region_settings.get('expire',
437 437 60))
438 438 region_settings.setdefault('lock_dir',
439 439 cache_settings.get('lock_dir'))
440 440 if 'type' not in region_settings:
441 441 region_settings['type'] = cache_settings.get('type',
442 442 'memory')
443 443 beaker.cache.cache_regions[region] = region_settings
444 444
445 445 def get_current_revision():
446 446 """
447 447 Returns tuple of (number, id) from repository containing this package
448 448 or None if repository could not be found.
449 449 """
450 450 try:
451 451 from vcs import get_repo
452 452 from vcs.utils.helpers import get_scm
453 453 from vcs.exceptions import RepositoryError, VCSError
454 454 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
455 455 scm = get_scm(repopath)[0]
456 456 repo = get_repo(path=repopath, alias=scm)
457 457 tip = repo.get_changeset()
458 458 return (tip.revision, tip.short_id)
459 459 except (ImportError, RepositoryError, VCSError), err:
460 460 logging.debug("Cannot retrieve rhodecode's revision. Original error "
461 461 "was: %s" % err)
462 462 return None
463 463
464 464 #===============================================================================
465 465 # TEST FUNCTIONS AND CREATORS
466 466 #===============================================================================
467 467 def create_test_index(repo_location, full_index):
468 468 """Makes default test index
469 469 :param repo_location:
470 470 :param full_index:
471 471 """
472 472 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
473 473 from rhodecode.lib.pidlock import DaemonLock, LockHeld
474 474 import shutil
475 475
476 476 index_location = os.path.join(repo_location, 'index')
477 477 if os.path.exists(index_location):
478 478 shutil.rmtree(index_location)
479 479
480 480 try:
481 481 l = DaemonLock()
482 482 WhooshIndexingDaemon(index_location=index_location,
483 483 repo_location=repo_location)\
484 484 .run(full_index=full_index)
485 485 l.release()
486 486 except LockHeld:
487 487 pass
488 488
489 489 def create_test_env(repos_test_path, config):
490 490 """Makes a fresh database and
491 491 install test repository into tmp dir
492 492 """
493 493 from rhodecode.lib.db_manage import DbManage
494 494 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
495 495 HG_FORK, GIT_FORK, TESTS_TMP_PATH
496 496 import tarfile
497 497 import shutil
498 498 from os.path import dirname as dn, join as jn, abspath
499 499
500 500 log = logging.getLogger('TestEnvCreator')
501 501 # create logger
502 502 log.setLevel(logging.DEBUG)
503 503 log.propagate = True
504 504 # create console handler and set level to debug
505 505 ch = logging.StreamHandler()
506 506 ch.setLevel(logging.DEBUG)
507 507
508 508 # create formatter
509 509 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
510 510
511 511 # add formatter to ch
512 512 ch.setFormatter(formatter)
513 513
514 514 # add ch to logger
515 515 log.addHandler(ch)
516 516
517 517 #PART ONE create db
518 518 dbconf = config['sqlalchemy.db1.url']
519 519 log.debug('making test db %s', dbconf)
520 520
521 521 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
522 522 tests=True)
523 523 dbmanage.create_tables(override=True)
524 524 dbmanage.config_prompt(repos_test_path)
525 525 dbmanage.create_default_user()
526 526 dbmanage.admin_prompt()
527 527 dbmanage.create_permissions()
528 528 dbmanage.populate_default_permissions()
529 529
530 530 #PART TWO make test repo
531 531 log.debug('making test vcs repositories')
532 532
533 533 #remove old one from previos tests
534 534 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
535 535
536 536 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
537 537 log.debug('removing %s', r)
538 538 shutil.rmtree(jn(TESTS_TMP_PATH, r))
539 539
540 540 #CREATE DEFAULT HG REPOSITORY
541 541 cur_dir = dn(dn(abspath(__file__)))
542 542 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
543 543 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
544 544 tar.close()
545 545
546 546
547 547 #==============================================================================
548 548 # PASTER COMMANDS
549 549 #==============================================================================
550 550
551 551 class BasePasterCommand(Command):
552 552 """
553 553 Abstract Base Class for paster commands.
554 554
555 555 The celery commands are somewhat aggressive about loading
556 556 celery.conf, and since our module sets the `CELERY_LOADER`
557 557 environment variable to our loader, we have to bootstrap a bit and
558 558 make sure we've had a chance to load the pylons config off of the
559 559 command line, otherwise everything fails.
560 560 """
561 561 min_args = 1
562 562 min_args_error = "Please provide a paster config file as an argument."
563 563 takes_config_file = 1
564 564 requires_config_file = True
565 565
566 def notify_msg(self, msg, log=False):
567 """Make a notification to user, additionally if logger is passed
568 it logs this action using given logger
569
570 :param msg: message that will be printed to user
571 :param log: logging instance, to use to additionally log this message
572
573 """
574 print msg
575 if log and isinstance(log, logging):
576 log(msg)
577
578
566 579 def run(self, args):
567 580 """
568 581 Overrides Command.run
569 582
570 583 Checks for a config file argument and loads it.
571 584 """
572 585 if len(args) < self.min_args:
573 586 raise BadCommand(
574 587 self.min_args_error % {'min_args': self.min_args,
575 588 'actual_args': len(args)})
576 589
577 590 # Decrement because we're going to lob off the first argument.
578 591 # @@ This is hacky
579 592 self.min_args -= 1
580 593 self.bootstrap_config(args[0])
581 594 self.update_parser()
582 595 return super(BasePasterCommand, self).run(args[1:])
583 596
584 597 def update_parser(self):
585 598 """
586 599 Abstract method. Allows for the class's parser to be updated
587 600 before the superclass's `run` method is called. Necessary to
588 601 allow options/arguments to be passed through to the underlying
589 602 celery command.
590 603 """
591 604 raise NotImplementedError("Abstract Method.")
592 605
593 606 def bootstrap_config(self, conf):
594 607 """
595 608 Loads the pylons configuration.
596 609 """
597 610 from pylons import config as pylonsconfig
598 611
599 612 path_to_ini_file = os.path.realpath(conf)
600 613 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
601 614 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
General Comments 0
You need to be logged in to leave comments. Login now