Blame view

install/install.py 19.6 KB
a86c87a7   jeremy   update for windows
1
import os, sys, subprocess, platform, fileinput, json, argparse, shutil
1e9aab20   Jeremy   Added install.py
2
3

DEBUG = False
1e9aab20   Jeremy   Added install.py
4

1e9aab20   Jeremy   Added install.py
5

ca58ff86   Jeremy   Update
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Utils:
    system = platform.system()
    columns = 100
    row = 1000

    def __init__(self):
        if (platform.system() != 'Windows'):
            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")

    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

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

    def replacePatternInFile(self, pattern, replace, file_path):
        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, 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
1e9aab20   Jeremy   Added install.py
52

1e9aab20   Jeremy   Added install.py
53
54
55
56
57
58

'''
    Python installer class
'''


ca58ff86   Jeremy   Update
59
class AInstaller(Utils):
1e9aab20   Jeremy   Added install.py
60
    path = os.path.realpath(__file__)
ca58ff86   Jeremy   Update
61
    path_dir = os.getcwd()
1e9aab20   Jeremy   Added install.py
62
63
    python_path = sys.executable
    python_version = sys.version_info
ca58ff86   Jeremy   Update
64

1e9aab20   Jeremy   Added install.py
65
    commands = []
ca58ff86   Jeremy   Update
66
67
68
69
70
    commandMatcher = {}
    bin_dir = ""
    venv_pip = ""
    venv_bin = ""
    venv = ""
a86c87a7   jeremy   update for windows
71
    venv_dir = ""
ca58ff86   Jeremy   Update
72
73
74
75
    requirements = ""

    current_command = "default"
    private = {}
1e9aab20   Jeremy   Added install.py
76
    commands_list = []
ca58ff86   Jeremy   Update
77
    to_print = []
1e9aab20   Jeremy   Added install.py
78
79
    executed = {}
    errors = {}
ca58ff86   Jeremy   Update
80
    defined_variables = {}
1e9aab20   Jeremy   Added install.py
81

ca58ff86   Jeremy   Update
82
83
84
85
86
87
88
89
90
91
92
    def __init__(self, config):
        super(AInstaller, self).__init__()
        self.private = {
            "requirements": self.requirement,
            "create": self.create,
        }
        self.config = config
        self.commands = self.config.getCommandList()
        self.venv = self.config.getPath() + os.sep + self.config.getEnv()
        self.requirements = self.config.getReq()
        self.adaptInstaller()
1e9aab20   Jeremy   Added install.py
93
        os.chdir(self.path_dir)
ca58ff86   Jeremy   Update
94
95
96
97

    def adaptInstaller(self):
        self.commandMatcher = self.config.getCommands()
        config = self.config.getConfig()
1e9aab20   Jeremy   Added install.py
98
        if self.system == 'Windows':
ca58ff86   Jeremy   Update
99
100
101
            self.bin_dir = "Scripts"
            self.bin_name = "python.exe"
            self.pip_name = "pip.exe"
1e9aab20   Jeremy   Added install.py
102
        else:
ca58ff86   Jeremy   Update
103
104
105
106
107
            self.bin_dir = "bin"
            self.bin_name = "python"
            self.pip_name = "pip"
        self.venv_pip = config["path"] + os.sep + config["env"] + os.sep + self.bin_dir + os.sep + self.pip_name
        self.venv_bin = config["path"] + os.sep + config["env"] + os.sep + self.bin_dir + os.sep + self.bin_name
a86c87a7   jeremy   update for windows
108
        self.site_packages = config["path"] + os.sep + config["env"] + os.sep + "Lib" + os.sep + "site-packages" + os.sep
ca58ff86   Jeremy   Update
109

1e9aab20   Jeremy   Added install.py
110
111

    '''
ca58ff86   Jeremy   Update
112
        Special functions
1e9aab20   Jeremy   Added install.py
113
    '''
ca58ff86   Jeremy   Update
114
115
116
    def requirement(self):
        if (os.path.isfile(self.requirements)):
            if self.execProcessFromVenv(self.venv_pip + " install -r " + self.requirements):
1e9aab20   Jeremy   Added install.py
117
                return 1
ca58ff86   Jeremy   Update
118
119
        else:
            self.printFullTerm(Colors.FAIL, "Requirements file " + self.requirements + " not found.")
1e9aab20   Jeremy   Added install.py
120
121
        return 0

ca58ff86   Jeremy   Update
122
123
124
    def create(self):
        if (not os.path.isdir(self.venv)):
            if self.execProcess("virtualenv " + self.venv + " -p " + self.python_path):
1e9aab20   Jeremy   Added install.py
125
                return 1
ca58ff86   Jeremy   Update
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
        else:
            self.printFullTerm(Colors.FAIL, "Virtual environment already exist")
        return 0

    '''
        Functions parameters
    '''
    def createConfig(self):
        configuration = self.config.createConfigFile()
        try:
            with open(self.config.getConfigFile(), "w") as f:
                f.write(json.dumps(configuration, sort_keys=True, indent=4))
        except Exception as e:
            print(e, file=sys.stderr)
            self.errors[self.current_command] = []
1e9aab20   Jeremy   Added install.py
141
            return 1
ca58ff86   Jeremy   Update
142
        self.executed[self.current_command] = []
1e9aab20   Jeremy   Added install.py
143
144
145
146
147
        return 0

    '''
        UTILS FUNCTIONS
    '''
ca58ff86   Jeremy   Update
148
149
150
    def setConfig(self, config):
        self.config = config

4816e86b   Jeremy   removed *.sh *.ba...
151
152
153
154
155
156
157
158
159
160
161
162
163
164
    def addExecuted(self, src, message):
        if (src in self.executed):
            self.executed[src].append(str(message))
        else:
            self.executed[src] = [str(message)]
        return 0

    def addError(self, src, message):
        if (src in self.errors):
            self.errors[src].append(str(message))
        else:
            self.errors[src] = [str(message)]
        return 0

ca58ff86   Jeremy   Update
165
    def execProcess(self, command):
1e9aab20   Jeremy   Added install.py
166
167
168
169
170
        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")
4816e86b   Jeremy   removed *.sh *.ba...
171
            self.addExecuted(self.current_command, command)
1e9aab20   Jeremy   Added install.py
172
173
        else:
            self.printFullTerm(Colors.WARNING, "Process execution failed")
4816e86b   Jeremy   removed *.sh *.ba...
174
            self.addError(self.current_command, command)
1e9aab20   Jeremy   Added install.py
175
176
        return process.returncode

ca58ff86   Jeremy   Update
177
    def execProcessFromVenv(self, command):
1e9aab20   Jeremy   Added install.py
178
        args = command.split()
ca58ff86   Jeremy   Update
179
        self.printFullTerm(Colors.BLUE, "Executing command from venv [" + str(' '.join(args[1:])) + "]")
1e9aab20   Jeremy   Added install.py
180
181
182
183
        process = subprocess.Popen(args)
        process.wait()
        if process.returncode == 0:
            self.printFullTerm(Colors.GREEN, "Process executed successfully")
4816e86b   Jeremy   removed *.sh *.ba...
184
            self.addExecuted(self.current_command, str(' '.join(args[1:])))
1e9aab20   Jeremy   Added install.py
185
186
        else:
            self.printFullTerm(Colors.WARNING, "Process execution failed")
4816e86b   Jeremy   removed *.sh *.ba...
187
            self.addError(self.current_command, str(' '.join(args[1:])))
1e9aab20   Jeremy   Added install.py
188
189
        return process.returncode

ca58ff86   Jeremy   Update
190
    def askYesNoQuestion(self,  message, question_tag, default=True):
1e9aab20   Jeremy   Added install.py
191
192
193
194
195
        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()
a86c87a7   jeremy   update for windows
196
        answer = sys.stdin.readline().replace('\n', '').replace('\r', '')
ca58ff86   Jeremy   Update
197
198
        if answer == "Y" or answer == "y":
            self.defined_variables[question_tag] = True
1e9aab20   Jeremy   Added install.py
199
200
            return True
        if (answer == ""):
ca58ff86   Jeremy   Update
201
            self.defined_variables[question_tag] = default
1e9aab20   Jeremy   Added install.py
202
            return default
ca58ff86   Jeremy   Update
203
        self.defined_variables[question_tag] = False
1e9aab20   Jeremy   Added install.py
204
205
        return False

ca58ff86   Jeremy   Update
206
    def askQuestion(self, message, default = ""):
1e9aab20   Jeremy   Added install.py
207
208
209
        self.printColor(Colors.BLUE, message)
        self.printColor(Colors.BOLD, "Answer (default="+default+"): ", eol='')
        sys.stdout.flush()
a86c87a7   jeremy   update for windows
210
        ret = sys.stdin.readline().replace('\n', '').replace('\r', '')
1e9aab20   Jeremy   Added install.py
211
212
213
214
        if ret == "":
            return default
        return ret

ca58ff86   Jeremy   Update
215
216
    def addToPrint(self, message):
        self.to_print.append(message)
1e9aab20   Jeremy   Added install.py
217
218
        return 0

ca58ff86   Jeremy   Update
219

1e9aab20   Jeremy   Added install.py
220
221
222
223

    '''
        CORE
    '''
ca58ff86   Jeremy   Update
224
    def feed(self, array):
1e9aab20   Jeremy   Added install.py
225
226
227
        self.commands_list = self.commands_list + array
        return 0

ca58ff86   Jeremy   Update
228
    def parse(self):
1e9aab20   Jeremy   Added install.py
229
230
231
232
233
234
235
236
237
238
        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

ca58ff86   Jeremy   Update
239
    def end(self):
1e9aab20   Jeremy   Added install.py
240
241
242
243
244
245
246
247
248
249
250
251
        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 : ")
ca58ff86   Jeremy   Update
252
253
        if not self.errors:
            self.printColor(Colors.GREEN, "\tNone")
1e9aab20   Jeremy   Added install.py
254
255
256
257
258
259
260
261
        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.")
ca58ff86   Jeremy   Update
262
263
264
265
        if not self.errors:
            self.printFullTerm(Colors.BOLD, "Messages")
            for message in self.to_print:
                self.printColor(Colors.BOLD, message)
1e9aab20   Jeremy   Added install.py
266
267
        return count

ca58ff86   Jeremy   Update
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
    def execCommand(self, command, matcher):
        if "normal" in matcher[command]:
            for c in matcher[command]["normal"]:
                if self.execProcess(c):
                    return 1
        if "env_pip" in matcher[command]:
            for c in matcher[command]["env_pip"]:
                if c != "" and c:
                    if self.execProcessFromVenv(self.venv_pip + " " + c):
                        return 1
        if "env_bin" in matcher[command]:
            for c in matcher[command]["env_bin"]:
                if c != "" and c:
                    if self.execProcessFromVenv(self.venv_bin + " " + c):
                        return 1
        if "private" in matcher[command]:
            for c in matcher[command]["private"]:
                if c in self.private:
                    if self.private[c]():
                        return 1
        if "link" in matcher[command]:
            for link in matcher[command]["link"]:
                if (link in matcher and link != self.current_command):
                    temp = self.current_command
                    self.current_command = link
                    if self.execCommand(link, matcher):
                        return 1
                    self.current_command = temp
        return 0

    def exec(self):
        if (not self.commands):
            return 0
1e9aab20   Jeremy   Added install.py
301
        for command in self.commands:
ca58ff86   Jeremy   Update
302
303
304
305
306
307
            self.current_command = command
            if command == "init":
                self.createConfig()
            elif command in self.commandMatcher:
                if self.execCommand(command, self.commandMatcher):
                    break
1e9aab20   Jeremy   Added install.py
308
309
310
311
312
            else:
                self.logError("Invalid command : " + str(command))
                self.errors[command] = []
        return self.end()

ca58ff86   Jeremy   Update
313
    def logError(self, message):
1e9aab20   Jeremy   Added install.py
314
315
316
        self.printColor(Colors.FAIL, "Installer : An error occurred [" + message + "]", file=sys.stderr)
        return 0

ca58ff86   Jeremy   Update
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
'''
    Config Class
'''


class Config:
    __parser = argparse.ArgumentParser("Installer parser")
    __config_file = "install.json"
    __content = {
        "requirements": "REQUIREMENTS.txt",
        "path": "",
        "name": "default",
        "env": "venv"
    }
    __commands = {"install": {
        "env_bin": [],
        "env_pip": [],
        "normal": [
            "pip install --upgrade pip",
            "pip install virtualenv --upgrade"
        ],
        "special": ["create"],
        "link": ["update"]
    }, "update": {
        "env_bin": [],
        "env_pip": [
            "install --upgrade pip",
            "install --upgrade wheel",
            ""
        ],
        "normal": [],
        "special": ["requirements"]
    }}
    usage = ""
    __command_list = []

    def __init__(self):
        self.__parser.add_argument("command", help="The command you want to execute")
        self.__parser.add_argument("--conf", help="Configuration file (.json)")
        self.__parser.add_argument("--path", help="Path where the virtual env will be created")
        self.__parser.add_argument("--name", help="Your configuration name")
        self.__parser.add_argument("--env", help="Your environment directory name")
        self.__parser.add_argument("--req", help="Your requirements file")
        self.usage = self.__parser.format_usage()

    def loadConfig(self):
        if not os.path.isfile(self.__config_file):
                return 0
        try:
            with open(self.__config_file) as data_file:
                data = json.load(data_file)
                for key, content in data.items():
                    if key == "Configuration":
                        for k, v in content.items():
                            self.__content[k] = v
                    else:
                        self.__commands[key] = {}
                        for k, v in content.items():
                            self.__commands[key][k] = v
                if (DEBUG):
                    print(json.dumps(self.__content, sort_keys=True, indent=4))
                    print(json.dumps(self.__commands, sort_keys=True, indent=4))
                return 0
        except Exception as e:
            print(e, file=sys.stderr)
            return 1

    def createConfigFile(self):
        configuration = {}
        configuration["Configuration"] = self.__content
        configuration["install"] = self.__commands["install"]
        configuration["update"] = self.__commands["update"]
        return configuration

    def parse(self):
        try:
            res = self.__parser.parse_args(), self.__parser.format_usage()
            return (res)
        except SystemExit as e:
4816e86b   Jeremy   removed *.sh *.ba...
396
            # print(e, file=sys.stderr)
ca58ff86   Jeremy   Update
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
            sys.exit(1)

    def parseConf(self):
        res, usage = self.parse()
        try:
            if (res.conf):
                self.__config_file = res.conf
            if self.loadConfig():
                return 1
            if (res.path):
                self.__content["path"] = res.path
            if (res.req):
                self.__content["requirements"] = res.req
            if (res.name):
                self.__content["name"] = res.name
            if (res.env):
                self.__content["env"] = res.env
            self.__command_list.append(res.command)
            if self.__content["path"] == "":
                self.__content["path"] = "."
            return 0
        except Exception as e:
            print(e, file=sys.stderr)
            return 1

    def getReq(self):
        return self.__content["requirements"]

    def getPath(self):
        return self.__content["path"]

    def getEnv(self):
        return self.__content["env"]

    def getCommandList(self):
        return self.__command_list

    def getConfigFile(self):
        return self.__config_file

    def getCommands(self):
        return self.__commands

    def printUsage(self):
        print(self.usage, file=sys.stderr)

    def addConf(self, key, value):
        if isinstance(key, str) and isinstance(value, str):
            self.__content[key] = value
            return 0
        return 1

    def setConfigFile(self, conf_file):
        if os.path.isfile(conf_file):
            self.__config_file = conf_file
            return 0
        return 1

    def setPath(self, path):
        if (os.path.isdir(path)):
            if (path == ""):
                path = "."
            self.__content["path"] = path
            return 0
        return 1

    def setEnvName(self, name):
        self.__content["env"] = name
        return 0

    def setRequirements(self, req):
        if (os.path.isfile(req)):
            self.__content["requirements"] = req
            return 0
        return 1

    def setConfName(self, name):
        self.__content["name"] = name
        return 0

    def getConfig(self):
        return self.__content

class Installer(AInstaller):
    sql = "CREATE DATABASE IF NOT EXISTS pyros; CREATE USER IF NOT EXISTS pyros; GRANT USAGE ON *.* TO 'pyros'; DROP USER 'pyros'; GRANT ALL ON pyros.* TO 'pyros'@'localhost' IDENTIFIED BY 'DjangoPyros'; GRANT ALL ON test_pyros.* TO 'pyros'@'localhost'"
    db_user = "pyros"
    db_password = "DjangoPyros"

    def __init__(self, config):
        super(Installer, self).__init__(config)
        # ADD YOUR PRIVATE FUNCTIONS HERE
        self.private["pyros"] = self.pyros
a86c87a7   jeremy   update for windows
489
490
        if self.system == "Windows":
            self.requirements = "REQUIREMENTS_WINDOWS.txt"
ca58ff86   Jeremy   Update
491
492
493
494
495
496
497
498
        self.addToPrint("Please run 'python pyrosrun.py server' then go to localhost:8000/admin and log in with these id")
        self.addToPrint("Username : " + self.db_user + " / Password : " + self.db_password)

    '''
        DEFINE YOUR PRIVATE FUNCTIONS HERE
    '''
    def pyros(self):
        if (self.current_command == "install"):
a86c87a7   jeremy   update for windows
499
500
501
502
503
504
505
506
507
508
509
510
511
            if (self.system == 'Windows'):
                try:
                    if (not os.path.isdir(self.site_packages + "voevent_parse-0.9.5.dist-info") and
                        not os.path.isdir(self.site_packages + "voeventparse")):
                        self.printFullTerm(Colors.BLUE, "Copying the voevent library in Lib/site-packages")
                        shutil.copytree("windows" + os.sep + "voevent_parse-0.9.5.dist-info", self.site_packages + "voevent_parse-0.9.5.dist-info")
                        shutil.copytree("windows" + os.sep + "voeventparse", self.site_packages + "voeventparse")
                except Exception as e:
                    self.printFullTerm(Colors.FAIL, "Execution failed")
                    self.addError(self.current_command, "Could not copy the voevent libs")
                    return 1
            if self.execProcessFromVenv(self.venv_pip + " install windows\\lxml-3.6.0-cp35-cp35m-win32.whl"):
                return 1
ca58ff86   Jeremy   Update
512
513
514
515
            if ("database" not in self.defined_variables):
                self.askYesNoQuestion("Do you wish to use mysql in your project ? only if mysql is installed", question_tag="database", default=False)
            if (self.defined_variables["database"]):
                username = self.askQuestion("MYSQL Username", default="root")
a86c87a7   jeremy   update for windows
516
517
518
519
520
521
522
523
                if (self.system == "Windows"):
                    if (self.execProcess("echo " + self.sql + "|mysql -u "+ username + " -p") or
                        self.replacePatternInFile("MYSQL = False", "MYSQL = True", "../src/pyros/settings.py")):
                        return 1
                else:
                    if (self.execProcess("echo \""+self.sql+"\" |mysql -u "+username+" -p") or
                        self.replacePatternInFile("MYSQL = False", "MYSQL = True", "../src/pyros/settings.py")):
                        return 1
ca58ff86   Jeremy   Update
524
525
526
527
528
        elif (self.current_command == "update"):
            if ("database" not in self.defined_variables):
                self.askYesNoQuestion("Do you wish to use mysql in your project ? only if mysql is installed",
                                      question_tag="database", default=False)
            if (self.defined_variables["database"]):
a86c87a7   jeremy   update for windows
529
530
531
532
533
534
                if (self.system == 'Windows'):
                    if self.execProcessFromVenv(self.venv_pip + " install windows\\mysqlclient-1.3.7-cp35-cp35m-win32.whl"):
                        return 1
                else:
                    if (self.execProcessFromVenv(self.venv_pip + " install mysqlclient==1.3.7")):
                        return 1
3b0ef1c7   Jeremy   Beautify help mes...
535
        self.execProcessFromVenv(self.venv_bin + " ../pyros.py init_database")
ca58ff86   Jeremy   Update
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
        return 0


'''
    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'

1e9aab20   Jeremy   Added install.py
554
if __name__ == "__main__":
ca58ff86   Jeremy   Update
555
556
557
558
559
    conf = Config()
    if conf.parseConf():
        sys.exit(1)
    installer = Installer(conf)
    sys.exit(installer.exec())