1# coding: UTF-8 2 3import sys 4import os 5import configparser 6import glob 7 8HAS_PYMYSQL = True 9try: 10 import pymysql 11except ImportError: 12 HAS_PYMYSQL = False 13 14HAS_SQLITE3 = True 15try: 16 import sqlite3 17except ImportError: 18 HAS_SQLITE3 = False 19 20class EnvManager(object): 21 def __init__(self): 22 self.upgrade_dir = os.path.dirname(__file__) 23 self.install_path = os.path.dirname(self.upgrade_dir) 24 self.top_dir = os.path.dirname(self.install_path) 25 self.ccnet_dir = os.environ['CCNET_CONF_DIR'] 26 self.seafile_dir = os.environ['SEAFILE_CONF_DIR'] 27 self.central_config_dir = os.environ.get('SEAFILE_CENTRAL_CONF_DIR') 28 29 30env_mgr = EnvManager() 31 32 33class Utils(object): 34 @staticmethod 35 def highlight(content, is_error=False): 36 '''Add ANSI color to content to get it highlighted on terminal''' 37 if is_error: 38 return '\x1b[1;31m%s\x1b[m' % content 39 else: 40 return '\x1b[1;32m%s\x1b[m' % content 41 42 @staticmethod 43 def info(msg): 44 print(Utils.highlight('[INFO] ') + msg) 45 46 @staticmethod 47 def warning(msg): 48 print(Utils.highlight('[WARNING] ') + msg) 49 50 @staticmethod 51 def error(msg): 52 print(Utils.highlight('[ERROR] ') + msg) 53 sys.exit(1) 54 55 @staticmethod 56 def read_config(config_path, defaults): 57 if not os.path.exists(config_path): 58 Utils.error('Config path %s doesn\'t exist, stop db upgrade' % 59 config_path) 60 cp = configparser.ConfigParser(defaults) 61 cp.read(config_path) 62 return cp 63 64 65class MySQLDBInfo(object): 66 def __init__(self, host, port, username, password, db, unix_socket=None): 67 self.host = host 68 self.port = port 69 self.username = username 70 self.password = password 71 self.db = db 72 self.unix_socket = unix_socket 73 74 75class DBUpdater(object): 76 def __init__(self, version, name): 77 self.sql_dir = os.path.join(env_mgr.upgrade_dir, 'sql', version, name) 78 pro_path = os.path.join(env_mgr.install_path, 'pro') 79 self.is_pro = os.path.exists(pro_path) 80 81 @staticmethod 82 def get_instance(version): 83 '''Detect whether we are using mysql or sqlite3''' 84 ccnet_db_info = DBUpdater.get_ccnet_mysql_info(version) 85 seafile_db_info = DBUpdater.get_seafile_mysql_info(version) 86 seahub_db_info = DBUpdater.get_seahub_mysql_info() 87 88 if ccnet_db_info and seafile_db_info and seahub_db_info: 89 Utils.info('You are using MySQL') 90 if not HAS_PYMYSQL: 91 Utils.error('Python pymysql module is not found') 92 updater = MySQLDBUpdater(version, ccnet_db_info, seafile_db_info, seahub_db_info) 93 94 elif (ccnet_db_info is None) and (seafile_db_info is None) and (seahub_db_info is None): 95 Utils.info('You are using SQLite3') 96 if not HAS_SQLITE3: 97 Utils.error('Python sqlite3 module is not found') 98 updater = SQLiteDBUpdater(version) 99 100 else: 101 def to_db_string(info): 102 if info is None: 103 return 'SQLite3' 104 else: 105 return 'MySQL' 106 Utils.error('Error:\n ccnet is using %s\n seafile is using %s\n seahub is using %s\n' 107 % (to_db_string(ccnet_db_info), 108 to_db_string(seafile_db_info), 109 to_db_string(seahub_db_info))) 110 111 return updater 112 113 def update_db(self): 114 ccnet_sql = os.path.join(self.sql_dir, 'ccnet.sql') 115 seafile_sql = os.path.join(self.sql_dir, 'seafile.sql') 116 seahub_sql = os.path.join(self.sql_dir, 'seahub.sql') 117 seafevents_sql = os.path.join(self.sql_dir, 'seafevents.sql') 118 119 if os.path.exists(ccnet_sql): 120 Utils.info('updating ccnet database...') 121 self.update_ccnet_sql(ccnet_sql) 122 123 if os.path.exists(seafile_sql): 124 Utils.info('updating seafile database...') 125 self.update_seafile_sql(seafile_sql) 126 127 if os.path.exists(seahub_sql): 128 Utils.info('updating seahub database...') 129 self.update_seahub_sql(seahub_sql) 130 131 if os.path.exists(seafevents_sql): 132 self.update_seafevents_sql(seafevents_sql) 133 134 @staticmethod 135 def get_ccnet_mysql_info(version): 136 if version > '5.0.0': 137 config_path = env_mgr.central_config_dir 138 else: 139 config_path = env_mgr.ccnet_dir 140 141 ccnet_conf = os.path.join(config_path, 'ccnet.conf') 142 defaults = { 143 'HOST': '127.0.0.1', 144 'PORT': '3306', 145 'UNIX_SOCKET': '', 146 } 147 148 config = Utils.read_config(ccnet_conf, defaults) 149 db_section = 'Database' 150 151 if not config.has_section(db_section): 152 return None 153 154 type = config.get(db_section, 'ENGINE') 155 if type != 'mysql': 156 return None 157 158 try: 159 host = config.get(db_section, 'HOST') 160 port = config.getint(db_section, 'PORT') 161 username = config.get(db_section, 'USER') 162 password = config.get(db_section, 'PASSWD') 163 db = config.get(db_section, 'DB') 164 unix_socket = config.get(db_section, 'UNIX_SOCKET') 165 except configparser.NoOptionError as e: 166 Utils.error('Database config in ccnet.conf is invalid: %s' % e) 167 168 info = MySQLDBInfo(host, port, username, password, db, unix_socket) 169 return info 170 171 @staticmethod 172 def get_seafile_mysql_info(version): 173 if version > '5.0.0': 174 config_path = env_mgr.central_config_dir 175 else: 176 config_path = env_mgr.seafile_dir 177 178 seafile_conf = os.path.join(config_path, 'seafile.conf') 179 defaults = { 180 'HOST': '127.0.0.1', 181 'PORT': '3306', 182 'UNIX_SOCKET': '', 183 } 184 config = Utils.read_config(seafile_conf, defaults) 185 db_section = 'database' 186 187 if not config.has_section(db_section): 188 return None 189 190 type = config.get(db_section, 'type') 191 if type != 'mysql': 192 return None 193 194 try: 195 host = config.get(db_section, 'host') 196 port = config.getint(db_section, 'port') 197 username = config.get(db_section, 'user') 198 password = config.get(db_section, 'password') 199 db = config.get(db_section, 'db_name') 200 unix_socket = config.get(db_section, 'unix_socket') 201 except configparser.NoOptionError as e: 202 Utils.error('Database config in seafile.conf is invalid: %s' % e) 203 204 info = MySQLDBInfo(host, port, username, password, db, unix_socket) 205 return info 206 207 @staticmethod 208 def get_seahub_mysql_info(): 209 sys.path.insert(0, env_mgr.top_dir) 210 if env_mgr.central_config_dir: 211 sys.path.insert(0, env_mgr.central_config_dir) 212 try: 213 import seahub_settings # pylint: disable=F0401 214 except ImportError as e: 215 Utils.error('Failed to import seahub_settings.py: %s' % e) 216 217 if not hasattr(seahub_settings, 'DATABASES'): 218 return None 219 220 try: 221 d = seahub_settings.DATABASES['default'] 222 if d['ENGINE'] != 'django.db.backends.mysql': 223 return None 224 225 host = d.get('HOST', '127.0.0.1') 226 port = int(d.get('PORT', 3306)) 227 username = d['USER'] 228 password = d['PASSWORD'] 229 db = d['NAME'] 230 unix_socket = host if host.startswith('/') else None 231 except KeyError: 232 Utils.error('Database config in seahub_settings.py is invalid: %s' % e) 233 234 info = MySQLDBInfo(host, port, username, password, db, unix_socket) 235 return info 236 237 def update_ccnet_sql(self, ccnet_sql): 238 raise NotImplementedError 239 240 def update_seafile_sql(self, seafile_sql): 241 raise NotImplementedError 242 243 def update_seahub_sql(self, seahub_sql): 244 raise NotImplementedError 245 246 def update_seafevents_sql(self, seafevents_sql): 247 raise NotImplementedError 248 249class CcnetSQLiteDB(object): 250 def __init__(self, ccnet_dir): 251 self.ccnet_dir = ccnet_dir 252 253 def get_db(self, dbname): 254 dbs = ( 255 'ccnet.db', 256 'GroupMgr/groupmgr.db', 257 'misc/config.db', 258 'OrgMgr/orgmgr.db', 259 'PeerMgr/usermgr.db', 260 ) 261 for db in dbs: 262 if os.path.splitext(os.path.basename(db))[0] == dbname: 263 return os.path.join(self.ccnet_dir, db) 264 265class SQLiteDBUpdater(DBUpdater): 266 def __init__(self, version): 267 DBUpdater.__init__(self, version, 'sqlite3') 268 269 self.ccnet_db = CcnetSQLiteDB(env_mgr.ccnet_dir) 270 self.seafile_db = os.path.join(env_mgr.seafile_dir, 'seafile.db') 271 self.seahub_db = os.path.join(env_mgr.top_dir, 'seahub.db') 272 self.seafevents_db = os.path.join(env_mgr.top_dir, 'seafevents.db') 273 274 def update_db(self): 275 super(SQLiteDBUpdater, self).update_db() 276 for sql_path in glob.glob(os.path.join(self.sql_dir, 'ccnet', '*.sql')): 277 self.update_ccnet_sql(sql_path) 278 279 def apply_sqls(self, db_path, sql_path): 280 with open(sql_path, 'r') as fp: 281 lines = fp.read().split(';') 282 283 with sqlite3.connect(db_path) as conn: 284 for line in lines: 285 line = line.strip() 286 if not line: 287 continue 288 else: 289 conn.execute(line) 290 291 def update_ccnet_sql(self, sql_path): 292 dbname = os.path.splitext(os.path.basename(sql_path))[0] 293 self.apply_sqls(self.ccnet_db.get_db(dbname), sql_path) 294 295 def update_seafile_sql(self, sql_path): 296 self.apply_sqls(self.seafile_db, sql_path) 297 298 def update_seahub_sql(self, sql_path): 299 self.apply_sqls(self.seahub_db, sql_path) 300 301 def update_seafevents_sql(self, sql_path): 302 if self.is_pro: 303 Utils.info('seafevents do not support sqlite3 database') 304 305 306class MySQLDBUpdater(DBUpdater): 307 def __init__(self, version, ccnet_db_info, seafile_db_info, seahub_db_info): 308 DBUpdater.__init__(self, version, 'mysql') 309 self.ccnet_db_info = ccnet_db_info 310 self.seafile_db_info = seafile_db_info 311 self.seahub_db_info = seahub_db_info 312 313 def update_ccnet_sql(self, ccnet_sql): 314 self.apply_sqls(self.ccnet_db_info, ccnet_sql) 315 316 def update_seafile_sql(self, seafile_sql): 317 self.apply_sqls(self.seafile_db_info, seafile_sql) 318 319 def update_seahub_sql(self, seahub_sql): 320 self.apply_sqls(self.seahub_db_info, seahub_sql) 321 322 def update_seafevents_sql(self, seafevents_sql): 323 if self.is_pro: 324 Utils.info('updating seafevents database...') 325 self.apply_sqls(self.seahub_db_info, seafevents_sql) 326 327 def get_conn(self, info): 328 kw = dict( 329 user=info.username, 330 passwd=info.password, 331 db=info.db, 332 ) 333 if info.unix_socket: 334 kw['unix_socket'] = info.unix_socket 335 else: 336 kw['host'] = info.host 337 kw['port'] = info.port 338 try: 339 conn = pymysql.connect(**kw) 340 except Exception as e: 341 if isinstance(e, pymysql.err.OperationalError): 342 msg = str(e.args[1]) 343 else: 344 msg = str(e) 345 Utils.error('Failed to connect to mysql database %s: %s' % (info.db, msg)) 346 347 return conn 348 349 def execute_sql(self, conn, sql): 350 cursor = conn.cursor() 351 try: 352 cursor.execute(sql) 353 conn.commit() 354 except Exception as e: 355 msg = str(e) 356 Utils.warning('Failed to execute sql: %s' % msg) 357 358 def apply_sqls(self, info, sql_path): 359 with open(sql_path, 'r') as fp: 360 lines = fp.read().split(';') 361 362 conn = self.get_conn(info) 363 364 for line in lines: 365 line = line.strip() 366 if not line: 367 continue 368 else: 369 self.execute_sql(conn, line) 370 371 372def main(): 373 skipdb = os.environ.get('SEAFILE_SKIP_DB_UPGRADE', '').lower() 374 if skipdb in ('1', 'true', 'on'): 375 print('Database upgrade skipped because SEAFILE_SKIP_DB_UPGRADE=%s' % skipdb) 376 sys.exit() 377 version = sys.argv[1] 378 db_updater = DBUpdater.get_instance(version) 379 db_updater.update_db() 380 381 return 0 382 383if __name__ == '__main__': 384 main() 385