Blame view

install/install.py 19.7 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

257abe9b   Jeremy   Added comments
5
6
7
8
'''
    Pyros installation file (able to handle project configuration commands)
'''

1e9aab20   Jeremy   Added install.py
9

ca58ff86   Jeremy   Update
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
52
53
54
55
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
56

1e9aab20   Jeremy   Added install.py
57
58
59
60
61
62

'''
    Python installer class
'''


ca58ff86   Jeremy   Update
63
class AInstaller(Utils):
1e9aab20   Jeremy   Added install.py
64
    path = os.path.realpath(__file__)
ca58ff86   Jeremy   Update
65
    path_dir = os.getcwd()
1e9aab20   Jeremy   Added install.py
66
67
    python_path = sys.executable
    python_version = sys.version_info
ca58ff86   Jeremy   Update
68

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

    current_command = "default"
    private = {}
1e9aab20   Jeremy   Added install.py
80
    commands_list = []
ca58ff86   Jeremy   Update
81
    to_print = []
1e9aab20   Jeremy   Added install.py
82
83
    executed = {}
    errors = {}
ca58ff86   Jeremy   Update
84
    defined_variables = {}
1e9aab20   Jeremy   Added install.py
85

ca58ff86   Jeremy   Update
86
87
88
89
90
91
92
93
94
95
96
    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
97
        os.chdir(self.path_dir)
ca58ff86   Jeremy   Update
98
99
100
101

    def adaptInstaller(self):
        self.commandMatcher = self.config.getCommands()
        config = self.config.getConfig()
1e9aab20   Jeremy   Added install.py
102
        if self.system == 'Windows':
ca58ff86   Jeremy   Update
103
104
105
            self.bin_dir = "Scripts"
            self.bin_name = "python.exe"
            self.pip_name = "pip.exe"
1e9aab20   Jeremy   Added install.py
106
        else:
ca58ff86   Jeremy   Update
107
108
109
110
111
            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
112
        self.site_packages = config["path"] + os.sep + config["env"] + os.sep + "Lib" + os.sep + "site-packages" + os.sep
ca58ff86   Jeremy   Update
113

1e9aab20   Jeremy   Added install.py
114
115

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

ca58ff86   Jeremy   Update
126
127
128
    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
129
                return 1
ca58ff86   Jeremy   Update
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
        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
145
            return 1
ca58ff86   Jeremy   Update
146
        self.executed[self.current_command] = []
1e9aab20   Jeremy   Added install.py
147
148
149
150
151
        return 0

    '''
        UTILS FUNCTIONS
    '''
ca58ff86   Jeremy   Update
152
153
154
    def setConfig(self, config):
        self.config = config

4816e86b   Jeremy   removed *.sh *.ba...
155
156
157
158
159
160
161
162
163
164
165
166
167
168
    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
169
    def execProcess(self, command):
1e9aab20   Jeremy   Added install.py
170
171
172
173
174
        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...
175
            self.addExecuted(self.current_command, command)
1e9aab20   Jeremy   Added install.py
176
177
        else:
            self.printFullTerm(Colors.WARNING, "Process execution failed")
4816e86b   Jeremy   removed *.sh *.ba...
178
            self.addError(self.current_command, command)
1e9aab20   Jeremy   Added install.py
179
180
        return process.returncode

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

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

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

ca58ff86   Jeremy   Update
219
220
    def addToPrint(self, message):
        self.to_print.append(message)
1e9aab20   Jeremy   Added install.py
221
222
        return 0

ca58ff86   Jeremy   Update
223

1e9aab20   Jeremy   Added install.py
224
225
226
227

    '''
        CORE
    '''
ca58ff86   Jeremy   Update
228
    def feed(self, array):
1e9aab20   Jeremy   Added install.py
229
230
231
        self.commands_list = self.commands_list + array
        return 0

ca58ff86   Jeremy   Update
232
    def parse(self):
1e9aab20   Jeremy   Added install.py
233
234
235
236
237
238
239
240
241
242
        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
243
    def end(self):
1e9aab20   Jeremy   Added install.py
244
245
246
247
248
249
250
251
252
253
254
255
        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
256
257
        if not self.errors:
            self.printColor(Colors.GREEN, "\tNone")
1e9aab20   Jeremy   Added install.py
258
259
260
261
262
263
264
265
        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
266
267
268
269
        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
270
271
        return count

ca58ff86   Jeremy   Update
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
301
302
303
304
    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
305
        for command in self.commands:
ca58ff86   Jeremy   Update
306
307
308
309
310
311
            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
312
313
314
315
316
            else:
                self.logError("Invalid command : " + str(command))
                self.errors[command] = []
        return self.end()

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

ca58ff86   Jeremy   Update
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
'''
    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": [
ca58ff86   Jeremy   Update
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
396
397
        ],
        "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...
398
            # print(e, file=sys.stderr)
ca58ff86   Jeremy   Update
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
            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):
4b503f5c   jeremy   Update installer ...
483
    sql = "CREATE DATABASE IF NOT EXISTS pyros; CREATE DATABASE IF NOT EXISTS pyros_test; 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'; GRANT ALL PRIVILEGES ON pyros_test.* TO 'pyros'@'localhost' WITH GRANT OPTION;"
ca58ff86   Jeremy   Update
484
485
486
487
488
489
490
    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
491
492
        if self.system == "Windows":
            self.requirements = "REQUIREMENTS_WINDOWS.txt"
ca58ff86   Jeremy   Update
493
494
495
496
497
498
499
500
        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
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
4b503f5c   jeremy   Update installer ...
512
513
                if self.execProcessFromVenv(self.venv_pip + " install windows\\lxml-3.6.0-cp35-cp35m-win32.whl"):
                    return 1
ca58ff86   Jeremy   Update
514
515
516
517
            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
518
519
520
521
522
523
524
525
                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
526
527
528
529
530
        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
531
532
533
534
535
536
                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
4b503f5c   jeremy   Update installer ...
537
            self.execProcessFromVenv(self.venv_bin + " ../pyros.py init_database")
ca58ff86   Jeremy   Update
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
        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
556
if __name__ == "__main__":
ca58ff86   Jeremy   Update
557
558
559
560
561
    conf = Config()
    if conf.parseConf():
        sys.exit(1)
    installer = Installer(conf)
    sys.exit(installer.exec())