install.py 11.2 KB
import os, sys, subprocess, platform, fileinput

DEBUG = False
DB_USER = "pyros"
DB_PASSWORD = "DjangoPyros"
SQL_SCRIPT = "CREATE DATABASE IF NOT EXISTS pyros; CREATE USER IF NOT EXISTS pyros; GRANT USAGE ON *.* TO '"+DB_USER+"'; DROP USER '"+DB_USER+"'; GRANT ALL ON pyros.* TO '"+DB_USER+"'@'localhost' IDENTIFIED BY '"+DB_PASSWORD+"'; GRANT ALL ON test_pyros.* TO '"+DB_USER+"'@'localhost'"

'''
    Color class
'''


class Colors:
    HEADER = '\033[95m'
    BLUE = '\033[94m'
    GREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

'''
    Python installer class
'''


class Installer:
    path = os.path.realpath(__file__)
    path_dir = os.path.dirname(os.path.realpath(__file__)) + os.sep
    python_path = sys.executable
    python_version = sys.version_info
    manage = " manage.py"
    requirements = "REQUIREMENTS.txt"
    venv_windows = "..\\venv\\Scripts\\"
    venv_linux = "../venv/bin/"
    current_command = "default"
    system = platform.system()
    yes_no_questions = {}
    row = 1000
    columns = 100
    commands = []
    commands_list = []
    commandMatcher = []
    toprint = []
    executed = {}
    errors = {}

    def __init__(self):
        os.chdir(self.path_dir)
        self.commandMatcher = {"install": self.install, "update": self.update}
        if (self.python_version.major != 3):
            self.printColor(Colors.FAIL, "This installer must be executed with python3.")
            sys.exit(2)
        if self.system == 'Windows':
            self.venv_pip = self.path_dir + self.venv_windows + "pip.exe"
            self.venv_bin = self.path_dir + self.venv_windows + "python.exe"
            self.requirements = "REQUIREMENTS_WINDOWS.txt"
        else:
            try:
                rows, columns = os.popen('stty size', 'r').read().split()
                self.columns = int(columns)
            except:
                self.columns = 100
                if DEBUG:
                    print("Could not get terminal size")
            self.venv_pip = self.path_dir + self.venv_linux + "pip"
            self.venv_bin = self.path_dir + self.venv_linux + "python"
        if DEBUG:
            print(self.venv_bin)
            print(self.venv_pip)

    '''
        Functions parameters
    '''
    def install(self) -> int:
        if (self.execProcess("pip install --upgrade pip")):
            return 1
        if (self.execProcess("pip install virtualenv --upgrade")):
            return 1
        self.changeDirectory("..")
        if (not os.path.isdir("venv")):
            self.execProcess("virtualenv venv" + " -p " + self.python_path)
        else:
            self.printFullTerm(Colors.WARNING, "Virtual environment already exist")
        self.changeDirectory(self.path_dir)
        ret = self.installDatabase()
        if (ret):
            return ret
        return self.update()

    def update(self) -> int:
        if (self.execProcessFromVenv(self.venv_pip + " install --upgrade pip") or
            self.execProcessFromVenv(self.venv_pip + " install --upgrade wheel") or
            self.execProcessFromVenv(self.venv_pip + " install -r " + self.requirements)):
            return 1
        return self.updateDatabase()

    def installDatabase(self):
        if ("database" not in self.yes_no_questions):
            self.askYesNoQuestion("Do you wish to use mysql in your project ? only if mysql is installed", question_tag="database", default=False)
        if (self.yes_no_questions["database"]):
            username = self.askQuestion("MYSQL Username", default="root")
            if (self.execProcess("echo \""+SQL_SCRIPT+"\" |mysql -u "+username+" -p") or
                self.replacePatternInFile("MYSQL = False", "MYSQL = True", "../src/pyros/settings.py")):
                return 1
        return 0

    def updateDatabase(self):
        context = os.getcwd()
        self.changeDirectory("../src/")
        if ("database" not in self.yes_no_questions):
            self.askYesNoQuestion("Do you wish to use mysql in your project ? only if mysql is installed", question_tag="database", default=False)
        if (self.yes_no_questions["database"]):
            if (self.execProcessFromVenv(self.venv_pip + " install mysqlclient==1.3.7")):
                return 1
        if (self.execProcessFromVenv(self.venv_bin + self.manage + " makemigrations") or
            self.execProcessFromVenv(self.venv_bin + self.manage + " migrate") or
            self.execProcessFromVenv(self.venv_bin + self.manage + " loaddata misc/fixtures/initial_fixture.json")):
            return 1
        self.changeDirectory(context)
        return 0

    '''
        UTILS FUNCTIONS
    '''
    def execProcess(self, command: str) -> int:
        self.printFullTerm(Colors.BLUE, "Executing command [" + command + "]")
        process = subprocess.Popen(command, shell=True)
        process.wait()
        if process.returncode == 0:
            self.printFullTerm(Colors.GREEN, "Process executed successfully")
            if (self.current_command in self.executed):
                self.executed[self.current_command].append(command)
            else:
                self.executed[self.current_command] = [command]
        else:
            self.printFullTerm(Colors.WARNING, "Process execution failed")
            if (self.current_command in self.errors):
                self.errors[self.current_command].append(command)
            else:
                self.errors[self.current_command] = [command]
        return process.returncode

    def execProcessFromVenv(self, command: str) -> int:
        args = command.split()
        self.printFullTerm(Colors.BLUE, "Executing command [" + str(' '.join(args[1:])) + "]")
        process = subprocess.Popen(args)
        process.wait()
        if process.returncode == 0:
            self.printFullTerm(Colors.GREEN, "Process executed successfully")
            if (self.current_command in self.executed):
                self.executed[self.current_command].append(str(' '.join(args[1:])))
            else:
                self.executed[self.current_command] = [str(' '.join(args[1:]))]
        else:
            self.printFullTerm(Colors.WARNING, "Process execution failed")
            if (self.current_command in self.errors):
                self.errors[self.current_command].append(str(' '.join(args[1:])))
            else:
                self.errors[self.current_command] = [str(' '.join(args[1:]))]
        return process.returncode

    def changeDirectory(self, path: str) -> int:
        if DEBUG:
            print("Moving to : " + path)
        os.chdir(path)
        if DEBUG:
            print("Current directory : " + str(os.getcwd()))
        return 0

    def replacePatternInFile(self, pattern: str, replace: str, file_path: str) -> int:
        try:
            with fileinput.FileInput(file_path, inplace=True, backup='.bak') as file:
                for line in file:
                    print(line.replace(pattern, replace), end='')
        except:
            return 1
        return 0

    def printColor(self, color, message: str, file=sys.stdout, eol=os.linesep):
        if (self.system == 'Windows'):
            print(message, file=file, end=eol)
        else:
            print(color + message + Colors.ENDC, file=file, end=eol)
        return 0

    def askYesNoQuestion(self,  message: str, question_tag: str, default: bool = True) -> bool:
        print(os.linesep * 2)
        self.printColor(Colors.BLUE, message + ": ", eol='')
        self.printColor(Colors.WARNING, "(default: " + ("Yes" if default else "No") + ") (Y/N)")
        self.printColor(Colors.BOLD, "Answer : ", eol='')
        sys.stdout.flush()
        answer = sys.stdin.readline().replace(os.linesep, '')
        if (answer == "Y"):
            self.yes_no_questions[question_tag] = True
            return True
        if (answer == ""):
            self.yes_no_questions[question_tag] = default
            return default
        self.yes_no_questions[question_tag] = False
        return False

    def askQuestion(self, message: str, default: str = ""):
        self.printColor(Colors.BLUE, message)
        self.printColor(Colors.BOLD, "Answer (default="+default+"): ", eol='')
        sys.stdout.flush()
        ret = sys.stdin.readline().replace(os.linesep, '')
        if ret == "":
            return default
        return ret

    def addToPrint(self, message: str) -> int:
        self.toprint.append(message)
        return 0

    def printFullTerm(self, color, string):
        value = int(self.columns / 2 - len(string) / 2)
        self.printColor(color, "-" * value, eol='')
        self.printColor(color, string, eol='')
        value += len(string)
        self.printColor(color, "-" * (self.columns - value))
        return 0

    '''
        CORE
    '''
    def feed(self, array) -> int:
        self.commands_list = self.commands_list + array
        return 0

    def parse(self) -> int:
        try:
            for command in self.commands_list:
                if not isinstance(command, str):
                    raise Exception("Invalid parameter + " + str(command))
                self.commands.append(command)
        except Exception as e:
            self.logError("An exception has been raised : " + str(e))
            return 1
        return 0

    def end(self) -> int:
        count = 0

        self.printFullTerm(Colors.WARNING, "Summary")
        self.printColor(Colors.GREEN, "Success : ")
        for command, valid in self.executed.items():
            if not valid:
                self.printColor(Colors.BLUE, "\t- Command : " + command + " successfully executed !")
            else:
                self.printColor(Colors.WARNING, "\t- In commmand : " + command)
                for exe in valid:
                    self.printColor(Colors.GREEN, "\t\t - Command : " + exe + " successfully executed ! ")
        self.printColor(Colors.FAIL, "Errors : ")
        for command, items in self.errors.items():
            count += 1
            if (not items):
                self.printColor(Colors.FAIL, "Command : " + command + " failed !")
            else:
                self.printColor(Colors.WARNING, "\t- In commmand : " + command)
                for exe in items:
                    self.printColor(Colors.FAIL, "\t\t - Command : " + exe + " failed.")
        for message in self.toprint:
            self.printColor(Colors.BOLD, message)
        return count

    def exec(self) -> int:
        for command in self.commands:
            if command in self.commandMatcher:
                self.current_command = command
                self.commandMatcher[command]()
            else:
                self.logError("Invalid command : " + str(command))
                self.errors[command] = []
        return self.end()

    def logError(self, message: str) -> int:
        self.printColor(Colors.FAIL, "Installer : An error occurred [" + message + "]", file=sys.stderr)
        return 0

if __name__ == "__main__":
    installer = Installer()
    installer.feed(sys.argv[1:])
    installer.addToPrint("Please run 'python pyrosrun.py server' then go to localhost:8000/admin and log in with these id")
    installer.addToPrint("Username : " + DB_USER + " / Password : " + DB_PASSWORD)
    if installer.parse() == 0:
        sys.exit(installer.exec())
    sys.exit(1)