Mini Shell

Direktori : /usr/share/lve/dbgovernor/modules/
Upload File :
Current File : //usr/share/lve/dbgovernor/modules/cpanel.py

# coding:utf-8

# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
#
"""
This module contains class for managing governor on cPanel server
"""
import os
import re
import shutil
import sys
import textwrap
import urllib.request, urllib.error, urllib.parse
import hashlib

from utilities import exec_command_out, grep, add_line, \
    service, remove_lines, write_file, replace_lines, touch, \
    is_package_installed, remove_packages, exec_command, \
    parse_rpm_name, service_symlink, bcolors, get_cl_num, is_ubuntu

from .base import InstallManager


class cPanelManager(InstallManager):
    """
    Installation manager for cPanel
    """

    def update_user_map_file(self):
        """
        Update user mapping file for cPanel
        """
        try:
            self._script_subprocess("dbgovernor_map")
        except RuntimeError as e:
            self.warning(str(e))

    def warning(self, orig_msg):
        """
        Warn user in case of errors during dbgovernor_map process and exit with exitcode 1.
        If CpuserNotInMap Exception occurred, advice user to run rebuild_dbmap script
        Otherwise, just show original exception
        :param orig_msg: original exception text
        """
        if 'CpuserNotInMap' in orig_msg:
            username = self.retrieve_username(orig_msg)
            print(bcolors.fail('cPanel user `{u}` does not exist in the database map.'.format(u=username)))
            print(bcolors.info('Try to perform the following command: /scripts/rebuild_dbmap {u}\n'
                               'and then run /usr/share/lve/dbgovernor/mysqlgovernor.py --dbupdate again.'.format(u=username)))
            print(bcolors.warning('If this does not help, please, '
                                  'contact cPanel support with the original error:\n{m}'.format(u=username,
                                                                                                m=orig_msg.split('\n')[1])))
        else:
            print(bcolors.fail(orig_msg))
        sys.exit(1)

    @staticmethod
    def retrieve_username(msg):
        """
        Try to get corrupted user name from the exception text (e.g. CpuserNotInMap Exception)
        :param msg: original exception text
        :return: username if retrieved, None otherwise
        """
        try:
            return re.findall(r'cPanel user “(.+)” does not exist in the database map.', msg)[0]
        except IndexError:
            return None

    def install_mysql_beta_testing_hooks(self):
        """
        Specific hooks
        """
        self.set_fs_suid_dumpable()
        self._script("cpanel-install-hooks")

    def update_mysql_hooks(self):
        """
        Update mysql hooks
        """
        self._script("cpanel-delete-hooks")
        self._script("cpanel-install-hooks")

    def _delete(self, installed_packages):
        """
        Remove installed packages
        """
        # through mysql --version cmd
        current_version = self._check_mysql_version()

        if os.path.exists("/etc/chkserv.d/db_governor"):
            os.remove("/etc/chkserv.d/db_governor")
        self._script("chek_mysql_rpms_local", "-d")
        self._script("cpanel-delete-hooks")

        if os.path.exists("/etc/mysqlupdisable"):
            os.remove("/etc/mysqlupdisable")

        if os.path.exists("/var/cpanel/rpm.versions.d/cloudlinux.versions"):
            os.remove("/var/cpanel/rpm.versions.d/cloudlinux.versions")

        if os.path.exists("/etc/cpupdate.conf.governor"):
            if os.path.exists("/etc/cpupdate.conf"):
                os.remove("/etc/cpupdate.conf")
            os.rename("/etc/cpupdate.conf.governor", "/etc/cpupdate.conf")

        self._mysqlservice("stop")
        # delete installed packages and restore native
        remove_packages(installed_packages)
        self.restore_mysql_packages(current_version)
        # remove governor package
        exec_command_out("rpm -e governor-mysql")
        exec_command_out("/scripts/upcp --force")

    def restore_mysql_packages(self, current_version):
        """
        Install legacy packages after --delete procedure
        """
        # According to July 2022 CPanel Letter, they will not be supporting MariaDB 10.7, 10.8, or 10.9
        # since they will now be reaching end-of-life sometime in 2023.
        # So we will need to add this to unsupported logic MYSQLG-730
        if current_version['full'] == 'mariadb104':
            print('{} is unsupported by cPanel, mariadb105 will be installed instead'.format(
                current_version['full']))
            current_version = {
                'short': '10.5',
                'mysql_type': 'mariadb',
                'full': 'mariadb105'
            }
        print('Restoring known packages for {}'.format(current_version['full']))
        targets = {
            'mysql55': 'MySQL55',
            'mysql56': 'MySQL56',
            'mariadb100': 'MariaDB100',
            'mariadb101': 'MariaDB101',
            'mariadb102': 'MariaDB102',
            'mariadb103': 'MariaDB103',
            'mariadb104': 'MariaDB104',
            'mariadb105': 'MariaDB105',
            'mariadb106': 'MariaDB106',
            'mariadb1011': 'MariaDB1011',
        }
        old = 'MySQL50,MySQL51,'  # old unsupported targets
        not_managed = ('mysql57', 'mysql80')  # latest mysql not managed by cPanel
        # clear rpm management for all known targets
        for t in targets.values():
            exec_command('/usr/local/cpanel/scripts/update_local_rpm_versions --del target_settings.%(target)s' % {'target': t})
        # disable mysql targets for upcp not to fix them!
        # for k in filter(lambda x: 'mariadb' not in x and x != current_version['full'], targets.keys()):
        #     exec_command('/usr/local/cpanel/scripts/update_local_rpm_versions --edit target_settings.%(target)s uninstalled' % {'target': targets[k]})

        # update mysql version in cPanel's configuration file
        exec_command(self._rel("scripts/set_cpanel_mysql_version.pm %s" % current_version['short']))

        if current_version['mysql_type'] == 'mariadb':
            # add repo, yum install mariadb pkgs
            self.install_mariadb(current_version['full'])
        elif current_version['full'] in not_managed:
            # add repo, yum install mysql57 or mysql80
            self.install_mysql_community(current_version['full'])
            # create mysql alias for mysqld service
            self.mysql_service_symlink()
        else:
            # enable current mysql target to rpm management
            t = targets.get(current_version['full'])
            if not t:
                raise RuntimeError('unknown target for RPM management: {}'.format(current_version['full']))
            exec_command('/usr/local/cpanel/scripts/update_local_rpm_versions --edit target_settings.%(target)s installed' % {'target': t})
            # fix legacy RPMs (works for mysql55 and mysql56 only)
            if os.path.exists("/scripts/check_cpanel_rpms"):
                exec_command_out(
                    "/scripts/check_cpanel_rpms --fix --targets={}".format(
                        old + ','.join(targets.values())))
            # clear RPM management targets for cPanel higher than 72 version,
            # because setting it to `installed` causes MySQL/MariaDB Upgrade interface errors
            if self.get_panel_version() > 72:
                exec_command('/usr/local/cpanel/scripts/update_local_rpm_versions --del target_settings.%(target)s' % {'target': t})

    def install_mariadb(self, version):
        """
        Install official MariaDB
        """
        pkgs = ('MariaDB-server', 'MariaDB-client', 'MariaDB-shared',
                'MariaDB-devel', 'MariaDB-compat',)

        # do not try to install MariaDB-compat on CL9
        if get_cl_num() >= 9:
            pkgs = [pkg for pkg in pkgs if pkg != 'MariaDB-compat']

        num = version.split('mariadb')[-1]
        cpanel_alter_repo = f'MariaDB{num}'

        # try to find preinstalled cPanel own repo for given version
        if not self.install_from_existing_repo(cpanel_alter_repo, pkgs):
            # prepare repo data
            print('Preparing official MariaDB repository...')
            dot_version = f'{num[:2]}.{num[2:]}'
            repo_data = f"""
                [mariadb]
                name = MariaDB
                baseurl = https://archive.mariadb.org/yum/{dot_version}/rhel/$releasever/$basearch
                gpgkey = https://archive.mariadb.org/PublicKey
                         https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
                module_hotfixes = 1
                enabled = 1
                gpgcheck = 1
                """
            with open('/etc/yum.repos.d/MariaDB.repo', 'w') as repo_file:
                repo_file.write(textwrap.dedent(repo_data))

            # install MariaDB packages
            print('Installing packages')
            exec_command(
                "yum install -y --disableexcludes=all --disablerepo=cl-mysql* --disablerepo=mysqclient* {pkgs}".format(
                    pkgs=' '.join(pkgs)))

    def install_mysql_community(self, version):
        """
        Install official MySQL 5.7 or MySQL 8.0, not managed by cPanel
        """
        pkgs = ('mysql-community-server', 'mysql-community-client',
               'mysql-community-common', 'mysql-community-libs')
        if get_cl_num() >= 8:
            cpanel_alter_repo = f'{version.capitalize()}-community'
        else:
            cpanel_alter_repo = f'{version}-community'

        # try to find preinstalled cPanel own repo for given version
        if not self.install_from_existing_repo(cpanel_alter_repo, pkgs):
            # prepare mysql-community repo
            if not exec_command('rpm -qa | grep mysql80-community', silent=True):
                self.download_and_install_mysql_repo()

            # select MySQL version
            print('Selected version %s' % version)
            exec_command('yum-config-manager --disable mysql*-community')
            exec_command('yum-config-manager --enable {version}-community'.format(version=version))

            # install mysql-community packages
            print(f'Installing packages from {version}-community')
            exec_command(
                "yum install -y --disableexcludes=all --disablerepo=cl-mysql* --disablerepo=mysqclient* {pkgs}".format(
                    pkgs=' '.join(pkgs)))

    @staticmethod
    def install_from_existing_repo(reponame, packages):
        """
        Try to install from existing repository and return result
        Args:
            reponame: repository name
            packages: list pf packages to install

        Returns: True in case of success, False otherwise

        """
        # try to find preinstalled cPanel own repo for given version
        if exec_command(
                f"yum repolist -y --enablerepo=* | grep {reponame} -c",
                True, True) != "0":
            print(f'Installing packages from {reponame}')
            exec_command(
                "yum install -y --disableexcludes=all --disablerepo=cl-mysql* --disablerepo=mysqclient* --enablerepo={repo} {pkgs}".format(
                    repo=reponame, pkgs=' '.join(packages)))
            return True
        return False

    def download_and_install_mysql_repo(self):
        """
        Download mysql80-community-release repository and install it locally
        """
        # download repo file
        url = "https://repo.mysql.com/mysql80-community-release-el{v}.rpm".format(v=self.cl_version)
        repo_file = os.path.join(self.SOURCE, 'mysql-community-release.rpm')
        repo_md5 = {
            6: 'ec5978dc1c9b79ef570e3bc08ba3d531',
            7: '42048ccae58835e40e37b68a3f8b91fb',
            8: 'e436076fe27a60ea88024cc3db4b1bb0',
            9: '4fa11545b76db63df0efe852e28c4d6b'
        }
        opener = urllib.request.build_opener()
        opener.addheaders = [('User-agent', 'Mozilla/5.0')]

        print('Downloading %s' % url)
        try:
            rpm = opener.open(url).read()
            with open(repo_file, 'wb') as f:
                f.write(rpm)
        except urllib.error.HTTPError as err:
            print('Failed to download MySQL repository file: {e}'.format(e=err))
            sys.exit(1)

        if hashlib.md5(open(repo_file, 'rb').read()).hexdigest() != repo_md5[self.cl_version]:
            print('Failed to download MySQL repository file. File is corrupted!')
            sys.exit(1)

        # install repo
        exec_command_out('yum localinstall -y --disableexcludes=all {}'.format(repo_file))

    def mysql_service_symlink(self):
        """
        Create mysql alias for mysqld service
        """
        service_symlink('mysqld', 'mysql')
        # delete version cache (for web-interface correct version detection)
        try:
            os.unlink('/var/cpanel/mysql_server_version_cache')
            os.unlink(f'{self.my_cnf_datadir}/mysql_upgrade_info')
        except Exception:
            pass

    def _after_install_new_packages(self):
        """
        cPanel triggers after install new packages to system
        """
        # cpanel script for restart mysql service
        exec_command_out("/scripts/restartsrv_mysql")

        print("db_governor checking: ")
        if is_package_installed("governor-mysql"):
            exec_command_out("chkconfig --level 35 db_governor on")
            service("restart", "db_governor")
            print("OK")
        else:
            print("FAILED")

        # print "The installation of MySQL for db_governor completed"

        if os.path.exists("/usr/local/cpanel/cpanel"):
            if os.path.exists(
                    "/usr/local/cpanel/scripts/update_local_rpm_versions"):
                shutil.copy2(self._rel("utils/cloudlinux.versions"), "/var/cpanel/rpm.versions.d/cloudlinux.versions")
            else:
                if not os.path.exists("/etc/cpupdate.conf.governor"):
                    self._get_mysqlup()
                touch("/etc/mysqlupdisable")

        self._script("cpanel-install-hooks")

        if os.path.exists("/usr/local/cpanel/cpanel") and \
                os.path.exists(
                    "/usr/local/cpanel/scripts/update_local_rpm_versions"):
            if os.path.exists("/etc/mysqlupdisable"):
                os.unlink("/etc/mysqlupdisable")
            remove_lines("/etc/cpupdate.conf", "MYSQLUP=never")
        if os.path.exists("/etc/chkserv.d") and os.path.exists(
                self._rel("utils/db_governor")):
            shutil.copy2(self._rel("utils/db_governor"),
                         "/etc/chkserv.d/db_governor")
        # call parent after_install
        InstallManager._after_install_new_packages(self)

    def _after_install_rollback(self):
        """
        Rollback after install triggers
        """
        # if os.path.exists("/etc/mysqlupdisable"):
        #     os.remove("/etc/mysqlupdisable")

        # if os.path.exists("/var/cpanel/rpm.versions.d/cloudlinux.versions"):
        #     os.remove("/var/cpanel/rpm.versions.d/cloudlinux.versions")

        # if os.path.exists("/etc/cpupdate.conf.governor"):
        #     if os.path.exists("/etc/cpupdate.conf"):
        #         os.remove("/etc/cpupdate.conf")
        #     os.rename("/etc/cpupdate.conf.governor", "/etc/cpupdate.conf")

        # exec_command_out(SOURCE+"cpanel/cpanel-delete-hooks")

        # exec_command_out("/scripts/upcp --force")
        # if os.path.exists("/scripts/check_cpanel_rpms"):
        #     exec_command_out("/scripts/check_cpanel_rpms --fix --targets=MySQL50,MySQL51,MySQL55,MySQL56,MariaDB")

    #############################
    #############################
    #############################
    # if os.path.exists("/var/cpanel/rpm.versions.d/cloudlinux.versions"):
    #     os.unlink("/var/cpanel/rpm.versions.d/cloudlinux.versions")

    # exec_command_out(SOURCE+"cpanel/cpanel-delete-hooks")

    # remove_lines("/etc/cpupdate.conf", "MYSQLUP=never")
    # if os.path.exists("/etc/cpupdate.conf.governor"):
    #     os.unlink("/etc/cpupdate.conf.governor")

    # if os.path.exists("/etc/mysqlupdisable"):
    #     os.unlink("/etc/mysqlupdisable")

    def _before_delete(self):
        """
        Disable mysql service monitoring
        """
        self.enable_mysql_monitor(False)

    def _after_delete(self):
        """
        Enable mysql service monitoring
        """
        # call parent first
        InstallManager._after_delete(self)
        self.enable_mysql_monitor()

    def _before_install(self):
        """
        Disable mysql service monitoring
        """
        self.enable_mysql_monitor(False)

    def _after_install(self):
        """
        Enable mysql service monitoring
        """
        # call parent first
        InstallManager._after_install(self)
        self.enable_mysql_monitor()

    @staticmethod
    def _get_mysqlup():
        """
        ? Set value for panel update MYSQLUP option
        """
        if os.path.exists("/etc/cpupdate.conf"):
            shutil.copy2("/etc/cpupdate.conf", "/etc/cpupdate.conf.governor")
            is_mysqlup = grep("/etc/cpupdate.conf", "MYSQLUP")
            if is_mysqlup:
                if not grep(is_mysqlup, "never$", True):
                    replace_lines("/etc/cpupdate.conf", "".join(is_mysqlup),
                                  "MYSQLUP=never")
            else:
                add_line("/etc/cpupdate.conf", "\nMYSQLUP=never\n")
        else:
            write_file("/etc/cpupdate.conf.governor", "")
            write_file("/etc/cpupdate.conf", "MYSQLUP=never\n")

    def _detect_version_if_auto(self):
        """
        Detect vesrion of MySQL if mysql.type is auto
        """
        if os.path.exists(self._rel("scripts/detect-cpanel-mysql-version.pm")):
            mysqlname_array = exec_command(
                self._rel("scripts/detect-cpanel-mysql-version.pm"))
            mysqlname = ""
            if len(mysqlname_array) > 0:
                mysqlname = mysqlname_array[0]
            if "mysql" in mysqlname or "mariadb" in mysqlname:
                return mysqlname.strip()
        return ""

    def _custom_download_of_rpm(self, package_name):
        """
        How we should to download installed MySQL package
        """
        if package_name == "+":
            return "yes"

        result = parse_rpm_name(package_name)
        if len(result) == 4:
            return exec_command(self._rel(
                "scripts/cpanel-mysql-url-detect.pm %s %s-%s" % (
                    result[0], result[1], result[2])), True)
        return ""

    def make_additional_panel_related_check(self):
        """
        Specific cPanel check
        :return:
        """
        if os.path.exists("/usr/local/cpanel/cpanel"):
            if os.path.exists(
                    "/usr/local/cpanel/scripts/update_local_rpm_versions") and \
                    os.path.exists(
                        "/var/cpanel/rpm.versions.d/cloudlinux.versions") and \
                    os.path.exists(
                        self._rel("utils/cloudlinux.versions")):
                shutil.copy2(self._rel("utils/cloudlinux.versions"),
                             "/var/cpanel/rpm.versions.d/cloudlinux.versions")
        return

    def unsupported_db_version(self, force=False):
        """
        Skip an installation if not supported db version has been set
        MariaDB 10.5 is supported starting from cPanel v.98
        """
        # According to July 2022 CPanel Letter, they will not be supporting MariaDB 10.7, 10.8, or 10.9
        # since they will now be reaching end-of-life sometime in 2023.
        # So we will need to add this to unsupported logic MYSQLG-730
        #
        # We also still don't support MariaDB 10.7, 10.8 and 10.9  and possibly won't do it.
        # So they are absent in mysql version list, and we have not check for them
        #UNSUPPORTED_MARIADB_VERSIONS = ['mariadb107', 'mariadb108', 'mariadb109']
        UNSUPPORTED_MARIADB_VERSIONS = [ ]

        super().unsupported_db_version(force)
        version = InstallManager._get_result_mysql_version(self)
        if version in UNSUPPORTED_MARIADB_VERSIONS:
            print(bcolors.fail(f"{version} is unsupported version for cPanel"))
            sys.exit(1)

        if version in ('mariadb104',) or (
                version == 'mariadb105' and self.get_panel_version() < 98):
            print(bcolors.fail(f"{version} is unsupported version for cPanel"))
            if not force:
                sys.exit(1)

    @staticmethod
    def enable_mysql_monitor(enable=True):
        """
        Enable or disable mysql monitoring
        :param enable: if True - enable monitor
                       if False - disable monitor
        """
        exec_command_out(
            "whmapi1 configureservice service=mysql enabled=1 monitored={}".format(int(enable)))

    @staticmethod
    def get_panel_version():
        """
        Retrieve cPanel current version from /usr/local/cpanel/version file
        :return: major version value
        """
        with open('/usr/local/cpanel/version', 'r') as content:
            full_version = content.read().strip()
        return int(full_version.split('.')[1])

    @staticmethod
    def prepare_statement_for_ubuntu():
        """For cPanel preparing system for governor.
        1. Skip if not ubuntu
        2. Remove /etc/apt/sources.list.d/mysql.list
        3. Remove unneeded packages
        """
        if not is_ubuntu():
            return

        mysql_list = '/etc/apt/sources.list.d/mysql.list'

        try:
            os.remove(mysql_list)
        except FileNotFoundError:
            print(f'{mysql_list} not exists. Already deleted')

        exec_command_out('apt-get update -o Dpkg::Options::=--force-confnew')

        exec_command_out('apt remove mysql-community-server -y')
        exec_command_out('apt remove mysql-client -y')
        exec_command_out('apt remove libmysqlclient21 libmysqlclient-dev mysql-community* -y')

        if is_package_installed('mysql-client'):
            exec_command_out('dpkg -P mysql-client')

Zerion Mini Shell 1.0