Sindbad~EG File Manager

Current Path : /opt/dedrads/
Upload File :
Current File : //opt/dedrads/innodb_converter.py

#!/usr/bin/imh-python3
"""Converts all MyISAM tables to InnoDB"""
#import list type
from pathlib import Path
import os
import sys
from typing import List, Dict,Tuple, Optional
from argparse import ArgumentParser
import logging
import pymysql

#check for root
if os.getuid() != 0:
    sys.exit('This script must be run as root')

logger = logging.getLogger('innodb_convert.py')
#log to /var/log/innodb_convert.log
logger.setLevel(logging.DEBUG)
#print info and above to log file and console
console_handler = logging.StreamHandler(sys.stderr)
console_handler.setLevel(logging.INFO)
logger.addHandler(console_handler)
file_handler = logging.FileHandler('/var/log/innodb_convert.log')
file_handler.setLevel(logging.DEBUG)
logger.addHandler(file_handler)


def confirm_backups(default='N'):
    """Confirms that backups have been taken, with configurable default (Y or N)"""
    prompt = f"Have you taken backups? (y/{default}) "
    answer = input(prompt).strip() or default
    if answer.lower() != 'y':
        prompt2 = f"Do you wish to continue anyway? (y/{default}) "
        answer2 = input(prompt2).strip() or default
        if answer2.lower() != 'y':
            sys.exit('Exiting...')

def get_db_config() -> dict:
    """Returns a dictionary of the MySQL configuration"""
    mysql_cnf = Path('/root/.my.cnf')
    if not mysql_cnf.exists():
        logger.error('Error: /root/.my.cnf does not exist')
        sys.exit(1)

    my_cnf_data = {}

    with open(mysql_cnf, 'r', encoding='utf-8') as file:
        for line in file:
            if line.startswith('['):#] this comment is to fix my IDE indent
                section = line.strip('[]\n',)
                my_cnf_data[section] = {}
            else:
                key, value = line.strip().split('=')
                value = value.replace("'", "").replace('"', '')
                my_cnf_data[section][key] = value

    db_config_dict = {
        'host': 'localhost',
        'user': my_cnf_data['client']['user'],
        'password': my_cnf_data['client']['password'],
        'database': 'mysql',
        'charset': 'utf8mb4',
        'cursorclass': pymysql.cursors.DictCursor
    }
    return db_config_dict

db_config = get_db_config()

def list_databases() -> List:
    """Returns a list of all databases"""
    databases = []
    try:
        with pymysql.connect(**db_config) as connection:
            with connection.cursor() as cursor:
                cursor.execute('SHOW DATABASES')
                databases = cursor.fetchall()
                databases = [x['Database'] for x in databases]
                return databases
    except pymysql.err.MySQLError as e:
        error = f'Error: {e}'
        sys.exit(error)


def list_tables(database: str) -> Tuple[Optional[str], Optional[List[str]]]:
    """Returns a list of all tables in a database"""
    tables = []
    try:
        with pymysql.connect(**db_config) as connection:
            with connection.cursor() as cursor:
                cursor.execute(f'SHOW TABLES FROM {database}')
                tables = cursor.fetchall()
                tables = [x['Tables_in_' + database] for x in tables]
                return None, tables
    except pymysql.err.MySQLError as e:
        error = f'Error on {database}: {e}'
        logger.error(error)
        return error, None

def check_engine(database: str, table: str) -> Tuple[Optional[str], Optional[str]]:
    """Returns the engine of a table"""
    try:
        with pymysql.connect(**db_config) as connection:
            with connection.cursor() as cursor:
                error = None
                cursor.execute(f'SHOW TABLE STATUS FROM {database} LIKE "{table}"')
                table_status = cursor.fetchone()
                return error,table_status['Engine']
    except pymysql.err.MySQLError as e:
        error = f'Error on {database}.{table}: {e}'
        logger.error(error)
        return error, None

def convert_table(database: str, table: str) -> Optional[str]:
    """Converts a table to InnoDB"""
    try:
        with pymysql.connect(**db_config) as connection:
            with connection.cursor() as cursor:
                cursor.execute(f'ALTER TABLE {database}.{table} ENGINE=InnoDB')
                return None
    except pymysql.err.MySQLError as e:
        error = f'Error: {e}'
        logger.error(error)
        return error

def get_mysql_innodb_settings() -> Tuple[List, Dict]:
    """Returns the innodb settings from my.cnf and my.cnf.d"""
    conf_dir = Path('/etc/my.cnf.d')
    conf_files = list(conf_dir.glob('*.cnf'))
    main_conf = Path('/etc/my.cnf')
    innodb_settings = {}
    key_errors = []
    conf_files.append(main_conf)
    for file in conf_files:
        innodb_settings[str(file.name)] = {}
        if file.exists():
            with file.open('r', encoding='utf-8') as f:
                for line in f:
                    if line.startswith('innodb_'):
                        try:
                            key, value = line.strip().split('=')
                            key = key.strip()
                        except ValueError:
                            key = line.strip()
                            value = ''
                        for _, settings in innodb_settings.items():
                            if key in settings:
                                error = f'Error: {key} is defined in multiple files'
                                key_errors.append(error)
                        innodb_settings[str(file.name)][key] = value
    return key_errors, innodb_settings

def clean_db_list(databases: List) -> List:
    """Removes system databases from the list"""
    skips = ['information_schema', 'performance_schema', 'mysql', 'sys', 'test']
    for database in skips:
        if database in databases:
            databases.remove(database)
    return databases

def process_databases(databases: List, args) -> List:
    """Processes all databases"""
    errors = []
    for database in databases:
        error,tables = list_tables(database)
        if args.table:
            if args.table in tables:
                tables = [args.table]
            else:
                error = f'Error: {args.table} does not exist in {database}'
                logger.error(error)
                errors.append(error)
                continue
        if error:
            errors.append(error)
            continue
        for table in tables:
            error,engine = check_engine(database, table)
            if error:
                errors.append(error)
                continue
            if args.check:
                logger.info('%s.%s is %s', database, table, engine)
                continue
            if engine.lower() == 'myisam':
                logger.info('Converting %s.%s to InnoDB', database, table)
                error = convert_table(database, table)
                if error:
                    errors.append(error)
                    continue
    return errors

def main():
    """Main function"""
    parser = ArgumentParser(description='Converts MyISAM tables to InnoDB')
    parser.add_argument('-d', '--database', help='Database to convert')
    parser.add_argument('-a', '--all', help='Convert all databases', action='store_true')
    parser.add_argument('--check', help='Check all databases', action='store_true')
    parser.add_argument('-t', '--table', help='Specify a table to convert')
    args = parser.parse_args()

    errors = []
    if not args.check:
        confirm_backups()
    databases = list_databases()
    databases = clean_db_list(databases)
    if args.database:
        database = args.database
        if not database in databases:
            error = f'Error: {database} does not exist or is a system database'
            logger.error(error)
            sys.exit(error)
        databases = [database]
    errors = process_databases(databases, args)
    key_errors, innodb_settings = get_mysql_innodb_settings()
    if len(errors) > 0 or len(key_errors) > 0:
        logger.error('Errors occurred:')
        for error in errors:
            logger.error("\t%s", error)
        for key_error in key_errors:
            logger.error("\t%s", key_error)
    logger.info('InnoDB settings:')
    for filename, settings in innodb_settings.items():
        logger.info('%s', filename)
        for key, value in settings.items() :
            if value == '':
                logger.info('\t%s', key)
            else:
                logger.info('\t%s = %s', key, value)

if __name__ == '__main__':
    main()

Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists