From 516ee1aa1b849ebec21055ce5223f386a4f0a604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CErick?= <“rerick38@live.com.mx”> Date: Thu, 28 Oct 2021 12:40:17 -0500 Subject: [PATCH] latest --- .gitignore | 1 + Snap-Tool/modules/ConfigurationClass.py | 24 +- Snap-Tool/modules/ElasticClass.py | 239 +++++++++-------- Snap-Tool/modules/FormClass.py | 248 +++++++++--------- Snap-Tool/modules/LoggerClass.py | 6 +- Snap-Tool/modules/TelegramClass.py | 22 +- Snap-Tool/modules/UtilsClass.py | 16 +- .../ConfigurationClass.cpython-36.pyc | Bin 8730 -> 8786 bytes .../__pycache__/ElasticClass.cpython-36.pyc | Bin 9371 -> 9692 bytes .../__pycache__/FormClass.cpython-36.pyc | Bin 20538 -> 21028 bytes .../__pycache__/LoggerClass.cpython-36.pyc | Bin 1297 -> 1296 bytes .../__pycache__/TelegramClass.cpython-36.pyc | Bin 5764 -> 5764 bytes .../__pycache__/UtilsClass.cpython-36.pyc | Bin 5383 -> 5493 bytes 13 files changed, 287 insertions(+), 269 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba0430d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ \ No newline at end of file diff --git a/Snap-Tool/modules/ConfigurationClass.py b/Snap-Tool/modules/ConfigurationClass.py index 42063cd..a3cb072 100644 --- a/Snap-Tool/modules/ConfigurationClass.py +++ b/Snap-Tool/modules/ConfigurationClass.py @@ -3,8 +3,7 @@ from modules.LoggerClass import Logger """ -Class that manages everything related to the Snap-Tool -configuration file. +Class that manages everything related to the Snap-Tool configuration file. """ class Configuration: """ @@ -18,7 +17,7 @@ class Configuration: logger = None """ - Property that stores an object of type FormDialogs. + Property that stores an object of type FormDialog. """ form_dialog = None @@ -32,6 +31,7 @@ class Configuration: Parameters: self -- An instantiated object of the Configuration class. + form_dialog -- FormDialog class object. """ def __init__(self, form_dialog): self.logger = Logger() @@ -60,7 +60,7 @@ def createConfiguration(self): valid_certificate = self.form_dialog.getDataYesOrNo("\nDo you want the certificate for SSL/TLS communication to be validated?", "Certificate Validation") if valid_certificate == "ok": data_conf.append(True) - path_cert_file = self.form_dialog.getFile('/etc/Snap-Tool', "Select the CA certificate:") + path_cert_file = self.form_dialog.getFile('/etc/Snap-Tool', "Select the CA certificate:", ".pem") data_conf.append(path_cert_file) else: data_conf.append(False) @@ -86,10 +86,10 @@ def createConfiguration(self): data_conf.append(False) self.createFileConfiguration(data_conf) if path.exists(self.conf_file): - self.form_dialog.d.msgbox("\nConfiguration file created", 7, 50, title = "Notification Message") + self.form_dialog.d.msgbox(text = "\nConfiguration file created", height = 7, width = 50, title = "Notification Message") self.logger.createSnapToolLog("Configuration file created", 2) else: - self.form_dialog.d.msgbox("\nError creating configuration file. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nError creating configuration file. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() """ @@ -190,13 +190,13 @@ def updateConfiguration(self): del data_conf['path_certificate'] data_conf['valid_certificate'] = False elif opt_valid_cert_true == "Certificate File": - path_cert_file = self.form_dialog.getFile(data_conf['path_certificate'], "Select the CA certificate:") + path_cert_file = self.form_dialog.getFile(data_conf['path_certificate'], "Select the CA certificate:", ".pem") data_conf['path_certificate'] = path_cert_file else: opt_valid_cert_false = self.form_dialog.getDataRadioList("Select a option:", options_valid_cert_false, "Certificate Validation") if opt_valid_cert_false == "Enable": data_conf['valid_certificate'] = True - path_cert_file = self.form_dialog.getFile('/etc/Snap-Tool', "Select the CA certificate:") + path_cert_file = self.form_dialog.getFile('/etc/Snap-Tool', "Select the CA certificate:", ".pem") valid_cert_json = { 'path_certificate' : path_cert_file } data_conf.update(valid_cert_json) else: @@ -205,7 +205,7 @@ def updateConfiguration(self): data_conf['use_ssl'] = True valid_certificate = self.form_dialog.getDataYesOrNo("\nDo you want the certificates for SSL/TLS communication to be validated?", "Certificate Validation") if valid_certificate == "ok": - path_cert_file = self.form_dialog.getFile('/etc/Snap-Tool', "Select the CA certificate:") + path_cert_file = self.form_dialog.getFile('/etc/Snap-Tool', "Select the CA certificate:", ".pem") valid_cert_json = { 'valid_certificate' : True, 'path_certificate' : path_cert_file } else: valid_cert_json = { 'valid_certificate' : False } @@ -258,14 +258,14 @@ def updateConfiguration(self): self.utils.createYamlFile(data_conf, self.conf_file, 'w') hash_data_conf_upd = self.utils.getHashToFile(self.conf_file) if hash_data_conf == hash_data_conf_upd: - self.form_dialog.d.msgbox("\nThe configuration file was not modified.", 7, 50, title = "Notification Message") + self.form_dialog.d.msgbox(text = "\nThe configuration file was not modified.", height = 7, width = 50, title = "Notification Message") else: self.logger.createSnapToolLog("The configuration file was modified", 1) - self.form_dialog.d.msgbox("\nThe configuration file was modified.", 7, 50, title = "Notification Message") + self.form_dialog.d.msgbox(text = "\nThe configuration file was modified.", height = 7, width = 50, title = "Notification Message") self.form_dialog.mainMenu() except (OSError, KeyError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nError modifying the configuration file. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nError modifying the configuration file. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() """ diff --git a/Snap-Tool/modules/ElasticClass.py b/Snap-Tool/modules/ElasticClass.py index 93b0fcf..1a31b5e 100644 --- a/Snap-Tool/modules/ElasticClass.py +++ b/Snap-Tool/modules/ElasticClass.py @@ -117,33 +117,11 @@ def getConnectionElastic(self): self.logger.createSnapToolLog("Established connection with: " + self.snap_tool_conf['es_host'] + ':' + str(self.snap_tool_conf['es_port']), 1) except (KeyError, exceptions.ConnectionError, exceptions.AuthenticationException, exceptions.AuthorizationException, InvalidURL) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to connect to ElasticSearch. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to connect to ElasticSearch. For more information, see the logs.", height = 8, width = 50, title = "Error Message") exit(1) else: return conn_es - """ - Method that creates a snapshot of an index. - - Parameters: - self -- An instantiated object of the Elastic class. - conn_es -- Object that contains the connection to ElasticSearch. - repository_name -- Repository where the snapshot will be stored. - index_name -- Name of the index to be backed up in the snapshot. - - Exceptions: - exceptions.RequestError -- Exception representing a 400 status code. - exceptions.NotFoundError -- Exception representing a 404 status code. - exceptions.AuthorizationException -- Exception representing a 403 status code. - """ - def createSnapshot(self, conn_es, repository_name, index_name): - try: - conn_es.snapshot.create(repository = repository_name, snapshot = index_name, body = { 'indices' : index_name, "include_global_state" : False }, wait_for_completion = False) - except (exceptions.AuthorizationException, exceptions.RequestError, exceptions.NotFoundError) as exception: - self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to create snapshot. For more information, see the logs.", 8, 50, title = "Error Message") - self.form_dialog.mainMenu() - """ Method that creates a repository type FS. @@ -165,7 +143,7 @@ def createRepositoryFS(self, conn_es, repository_name, path_repository, compress body = { "type": "fs", "settings": { "location": path_repository, "compress" : compress_repository }}) except (exceptions.AuthorizationException, exceptions.ConnectionError, exceptions.TransportError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nError creating repository. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nError creating repository. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() """ @@ -179,155 +157,177 @@ def createRepositoryFS(self, conn_es, repository_name, path_repository, compress Exceptions: exceptions.NotFoundError -- Exception representing a 404 status code. exceptions.AuthorizationException -- Exception representing a 403 status code. + exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. """ def deleteRepositoryFS(self, conn_es, repository_name): try: conn_es.snapshot.delete_repository(repository = repository_name) - except (exceptions.NotFoundError, exceptions.AuthorizationException) as exception: + except (exceptions.NotFoundError, exceptions.AuthorizationException, exceptions.ConnectionError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to delete repository. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to delete repository. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() """ - Method that gets the status of a snapshot. + Method that creates a snapshot of an index. Parameters: self -- An instantiated object of the Elastic class. conn_es -- Object that contains the connection to ElasticSearch. - repository_name -- Repository where the snapshot is stored. - snapshot_name -- Name of the snapshot from which the status will be obtained. + repository_name -- Repository where the snapshot will be stored. + index_name -- Name of the index to be backed up in the snapshot. - Return: - status_snapshot -- Status of the snapshot. + Exceptions: + exceptions.RequestError -- Exception representing a 400 status code. + exceptions.NotFoundError -- Exception representing a 404 status code. + exceptions.AuthorizationException -- Exception representing a 403 status code. + exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. + """ + def createSnapshot(self, conn_es, repository_name, index_name): + try: + conn_es.snapshot.create(repository = repository_name, snapshot = index_name, body = { 'indices' : index_name, "include_global_state" : False }, wait_for_completion = False) + except (exceptions.AuthorizationException, exceptions.RequestError, exceptions.NotFoundError, exceptions.ConnectionError) as exception: + self.logger.createSnapToolLog(exception, 3) + self.form_dialog.d.msgbox(text = "\nFailed to create snapshot. For more information, see the logs.", height = 8, width = 50, title = "Error Message") + self.form_dialog.mainMenu() + + """ + Method that removes a snapshot. + + Parameters: + conn_es -- Object that contains the connection to ElasticSearch. + repository_name -- Name of the repository where the snapshot to delete is stored. + snapshot_name -- Name of the snapshot to delete. Exceptions: exceptions.NotFoundError -- Exception representing a 404 status code. exceptions.AuthorizationException -- Exception representing a 403 status code. + exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. """ - def getStatusSnapshot(self, conn_es, repository_name, snapshot_name): + def deleteSnapshotElastic(self, conn_es, repository_name, snapshot_name): try: - status_aux = conn_es.snapshot.status(repository = repository_name, snapshot = snapshot_name) - status_snapshot = status_aux['snapshots'][0]['state'] - except (exceptions.NotFoundError, exceptions.AuthorizationException) as exception: + conn_es.snapshot.delete(repository = repository_name, snapshot = snapshot_name, request_timeout = 7200) + except (exceptions.NotFoundError, exceptions.AuthorizationException, exceptions.ConnectionError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to get snapshot status. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to delete snapshot. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() - else: - return status_snapshot """ - Method that gets the status of a snapshot. + Method that restores a snapshot. Parameters: - self -- An instantiated object of the Elastic class. conn_es -- Object that contains the connection to ElasticSearch. repository_name -- Repository where the snapshot is stored. - snapshot_name -- Name of the snapshot from which the information will be obtained. - - Return: - snapshot_info -- Information obtained from the snapshot. + snapshot_name -- Name of the snapshot to restore. Exceptions: exceptions.NotFoundError -- Exception representing a 404 status code. exceptions.AuthorizationException -- Exception representing a 403 status code. + exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. """ - def getSnapshotInfo(self, conn_es, repository_name, snapshot_name): + def restoreSnapshot(self, conn_es, repository_name, snapshot_name): try: - snapshot_info = conn_es.snapshot.get(repository = repository_name, snapshot = snapshot_name) - except (exceptions.NotFoundError, exceptions.AuthorizationException) as exception: + conn_es.snapshot.restore(repository = repository_name, snapshot = snapshot_name) + except (exceptions.NotFoundError, exceptions.AuthorizationException, exceptions.ConnectionError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to get snapshot status. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to restore snapshot. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() - else: - return snapshot_info """ - Method that gets a list of all the snapshots created so far. + Method that mounts a snapshot as a searchable snapshot. Parameters: conn_es -- Object that contains the connection to ElasticSearch. - repository_name -- Repository where the snapshots are stored. - - Return: - list_all_snapshots -- List with the names of all snapshots found in the repository. + repository_name -- Name of the repository where the snapshot that will be mounted as a searchable snapshot is stored. + snapshot_name -- Name of the snapshot to be mounted as a searchable snapshot. Exceptions: exceptions.NotFoundError -- Exception representing a 404 status code. exceptions.AuthorizationException -- Exception representing a 403 status code. + exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. """ - def getAllSnapshots(self, conn_es, repository_name): - list_all_snapshots = [] + def mountSearchableSnapshot(self, conn_es, repository_name, snapshot_name): try: - snapshots_info = conn_es.snapshot.get(repository = repository_name, snapshot = '_all') - for snapshot in snapshots_info['snapshots']: - list_all_snapshots.append(snapshot['snapshot']) - list_all_snapshots = sorted(list_all_snapshots) - except (exceptions.NotFoundError, exceptions.AuthorizationException) as exception: + conn_es.searchable_snapshots.mount(repository = repository_name, snapshot = snapshot_name, body = { "index" : snapshot_name }, wait_for_completion = False, request_timeout = 7200) + except (exceptions.NotFoundError, exceptions.AuthorizationException, exceptions.ConnectionError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to get snapshots. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to mount snapshot as a searchable snapshot. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() - else: - return list_all_snapshots """ - Method that restores a snapshot. + Method that removes an index from ElasticSearch. Parameters: + self -- An instantiated object of the Elastic class. conn_es -- Object that contains the connection to ElasticSearch. - repository_name -- Repository where the snapshot is stored. - snapshot_name -- Name of the snapshot to restore. + index_name -- Name of the index to be removed. Exceptions: exceptions.NotFoundError -- Exception representing a 404 status code. exceptions.AuthorizationException -- Exception representing a 403 status code. + exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. """ - def restoreSnapshot(self, conn_es, repository_name, snapshot_name): + def deleteIndex(self, conn_es, index_name): try: - conn_es.snapshot.restore(repository = repository_name, snapshot = snapshot_name) - except (exceptions.NotFoundError, exceptions.AuthorizationException) as exception: + conn_es.indices.delete(index = index_name) + except (exceptions.NotFoundError, exceptions.AuthorizationException, exceptions.ConnectionError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to restore snapshot. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to delete index. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() - + """ - Method that removes a snapshot. + Method that gets the status of a snapshot. Parameters: + self -- An instantiated object of the Elastic class. conn_es -- Object that contains the connection to ElasticSearch. - repository_name -- Name of the repository where the snapshot to delete is stored. - snapshot_name -- Name of the snapshot to delete. + repository_name -- Repository where the snapshot is stored. + snapshot_name -- Name of the snapshot from which the status will be obtained. + + Return: + status_snapshot -- Status of the snapshot. Exceptions: exceptions.NotFoundError -- Exception representing a 404 status code. exceptions.AuthorizationException -- Exception representing a 403 status code. + exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. """ - def deleteSnapshotElastic(self, conn_es, repository_name, snapshot_name): + def getStatusSnapshot(self, conn_es, repository_name, snapshot_name): try: - conn_es.snapshot.delete(repository = repository_name, snapshot = snapshot_name, request_timeout = 7200) - except (exceptions.NotFoundError, exceptions.AuthorizationException) as exception: + status_aux = conn_es.snapshot.status(repository = repository_name, snapshot = snapshot_name) + status_snapshot = status_aux['snapshots'][0]['state'] + except (exceptions.NotFoundError, exceptions.AuthorizationException, exceptions.ConnectionError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to delete snapshot. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to get snapshot status. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() + else: + return status_snapshot """ - Method that mounts a snapshot as a searchable snapshot. + Method that gets the status of a snapshot. Parameters: + self -- An instantiated object of the Elastic class. conn_es -- Object that contains the connection to ElasticSearch. - repository_name -- Name of the repository where the snapshot that will be mounted as a searchable snapshot is stored. - snapshot_name -- Name of the snapshot to be mounted as a searchable snapshot. + repository_name -- Repository where the snapshot is stored. + snapshot_name -- Name of the snapshot from which the information will be obtained. + + Return: + snapshot_info -- Information obtained from the snapshot. Exceptions: exceptions.NotFoundError -- Exception representing a 404 status code. exceptions.AuthorizationException -- Exception representing a 403 status code. + exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. """ - def mountSearchableSnapshot(self, conn_es, repository_name, snapshot_name): + def getSnapshotInfo(self, conn_es, repository_name, snapshot_name): try: - conn_es.searchable_snapshots.mount(repository = repository_name, snapshot = snapshot_name, body = { "index" : snapshot_name }, wait_for_completion = False, request_timeout = 7200) - except (exceptions.NotFoundError, exceptions.AuthorizationException) as exception: + snapshot_info = conn_es.snapshot.get(repository = repository_name, snapshot = snapshot_name) + except (exceptions.NotFoundError, exceptions.AuthorizationException, exceptions.ConnectionError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to mount snapshot as a searchable snapshot. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to get snapshot status. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() + else: + return snapshot_info """ Method that obtains a list with the names of the ElasticSearch indexes. @@ -341,82 +341,93 @@ def mountSearchableSnapshot(self, conn_es, repository_name, snapshot_name): Exceptions: exceptions.AuthorizationException -- Exception representing a 403 status code. + exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. """ def getIndices(self, conn_es): list_all_indices = [] try: list_all_indices = conn_es.indices.get('*') list_all_indices = sorted([index for index in list_all_indices if not index.startswith(".")]) - except exceptions.AuthorizationException as exception: + except (exceptions.AuthorizationException, exceptions.ConnectionError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to get created repositories. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to get created repositories. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() else: return list_all_indices """ - Method that gets the repositories created in ElasticSearch. + Method that gets a list of all the snapshots created so far. Parameters: - self -- An instantiated object of the Elastic class. conn_es -- Object that contains the connection to ElasticSearch. + repository_name -- Repository where the snapshots are stored. Return: - list_all_repositories -- List with the names of the repositories found. + list_all_snapshots -- List with the names of all snapshots found in the repository. Exceptions: + exceptions.NotFoundError -- Exception representing a 404 status code. exceptions.AuthorizationException -- Exception representing a 403 status code. + exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. """ - def getAllRepositories(self, conn_es): - list_all_repositories = [] + def getAllSnapshots(self, conn_es, repository_name): + list_all_snapshots = [] try: - repositories_info = conn_es.cat.repositories(format = "json") - for repository in repositories_info: - list_all_repositories.append(repository['id']) - except exceptions.AuthorizationException as exception: + snapshots_info = conn_es.snapshot.get(repository = repository_name, snapshot = '_all') + for snapshot in snapshots_info['snapshots']: + list_all_snapshots.append(snapshot['snapshot']) + list_all_snapshots = sorted(list_all_snapshots) + except (exceptions.NotFoundError, exceptions.AuthorizationException, exceptions.ConnectionError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to get created repositories. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to get snapshots. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() else: - return list_all_repositories + return list_all_snapshots """ - Method that obtains information related to the disk space corresponding to the nodes belonging to the elasticsearch cluster. + Method that gets the repositories created in ElasticSearch. Parameters: self -- An instantiated object of the Elastic class. conn_es -- Object that contains the connection to ElasticSearch. + Return: + list_all_repositories -- List with the names of the repositories found. + Exceptions: exceptions.AuthorizationException -- Exception representing a 403 status code. + exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. """ - def getNodesInformation(self, conn_es): + def getAllRepositories(self, conn_es): + list_all_repositories = [] try: - nodes_info = conn_es.nodes.stats(metric = 'fs')['nodes'] - except exceptions.AuthorizationException as exception: + repositories_info = conn_es.cat.repositories(format = "json") + for repository in repositories_info: + list_all_repositories.append(repository['id']) + except (exceptions.AuthorizationException, exceptions.ConnectionError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nError when obtaining the information of the nodes. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to get created repositories. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() else: - return nodes_info + return list_all_repositories """ - Method that removes an index from ElasticSearch. + Method that obtains information related to the disk space corresponding to the nodes belonging to the elasticsearch cluster. Parameters: self -- An instantiated object of the Elastic class. conn_es -- Object that contains the connection to ElasticSearch. - index_name -- Name of the index to be removed. Exceptions: - exceptions.NotFoundError -- Exception representing a 404 status code. exceptions.AuthorizationException -- Exception representing a 403 status code. exceptions.ConnectionError -- Error raised when there was an exception while talking to ES. """ - def deleteIndex(self, conn_es, index_name): + def getNodesInformation(self, conn_es): try: - conn_es.indices.delete(index = index_name) - except (exceptions.NotFoundError, exceptions.AuthorizationException, exceptions.ConnectionError) as exception: + nodes_info = conn_es.nodes.stats(metric = 'fs')['nodes'] + except (exceptions.AuthorizationException, exceptions.ConnectionError) as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to delete index. For more information, see the logs.", 8, 50, title = "Error Message") - self.form_dialog.mainMenu() \ No newline at end of file + self.form_dialog.d.msgbox(text = "\nError when obtaining the information of the nodes. For more information, see the logs.", height = 8, width = 50, title = "Error Message") + self.form_dialog.mainMenu() + else: + return nodes_info \ No newline at end of file diff --git a/Snap-Tool/modules/FormClass.py b/Snap-Tool/modules/FormClass.py index efdd15e..da4836d 100644 --- a/Snap-Tool/modules/FormClass.py +++ b/Snap-Tool/modules/FormClass.py @@ -100,7 +100,7 @@ def getDataNumberDecimal(self, text, initial_value): init = initial_value) if code_inputbox == self.d.OK: if(not self.utils.validateRegularExpression(decimal_reg_exp, tag_inputbox)): - self.d.msgbox("\nInvalid data entered. Required value (decimal or float).", 8, 50, title = "Error Message") + self.d.msgbox(text = "\nInvalid data entered. Required value (decimal or float).", height = 8, width = 50, title = "Error Message") else: return tag_inputbox elif code_inputbox == self.d.CANCEL: @@ -126,7 +126,7 @@ def getDataIP(self, text, initial_value): init = initial_value) if code_inputbox == self.d.OK: if(not self.utils.validateRegularExpression(ip_reg_exp, tag_inputbox)): - self.d.msgbox("\nInvalid data entered. Required value (IP address).", 8, 50, title = "Error Message") + self.d.msgbox(text = "\nInvalid data entered. Required value (IP address).", height = 8, width = 50, title = "Error Message") else: return tag_inputbox elif code_inputbox == self.d.CANCEL: @@ -152,7 +152,7 @@ def getDataPort(self, text, initial_value): init = initial_value) if code_inputbox == self.d.OK: if(not self.utils.validateRegularExpression(port_reg_exp, tag_inputbox)): - self.d.msgbox("\nInvalid data entered. Required value (0 - 65535).", 8, 50, title = "Error Message") + self.d.msgbox(text = "\nInvalid data entered. Required value (0 - 65535).", height = 8, width = 50, title = "Error Message") else: return tag_inputbox elif code_inputbox == self.d.CANCEL: @@ -177,7 +177,7 @@ def getDataInputText(self, text, initial_value): init = initial_value) if code_inputbox == self.d.OK: if tag_inputbox == "": - self.d.msgbox("\nInvalid data entered. Required value (not empty).", 8, 50, title = "Error Message") + self.d.msgbox(text = "\nInvalid data entered. Required value (not empty).", height = 8, width = 50, title = "Error Message") else: return tag_inputbox elif code_inputbox == self.d.CANCEL: @@ -203,7 +203,7 @@ def getDataPassword(self, text, initial_value): insecure = True) if code_passwordbox == self.d.OK: if tag_passwordbox == "": - self.d.msgbox("\nInvalid data entered. Required value (not empty).", 8, 50, title = "Error Message") + self.d.msgbox(text = "\nInvalid data entered. Required value (not empty).", height = 8, width = 50, title = "Error Message") else: return tag_passwordbox elif code_passwordbox == self.d.CANCEL: @@ -247,7 +247,7 @@ def getDataRadioList(self, text, options, title): title = title) if code_radiolist == self.d.OK: if len(tag_radiolist) == 0: - self.d.msgbox("\nSelect at least one option.", 7, 50, title = "Error Message") + self.d.msgbox(text = "\nSelect at least one option.", height = 7, width = 50, title = "Error Message") else: return tag_radiolist elif code_radiolist == self.d.CANCEL: @@ -273,7 +273,7 @@ def getDataCheckList(self, text, options, title): title = title) if code_checklist == self.d.OK: if len(tag_checklist) == 0: - self.d.msgbox("\nSelect at least one option.", 7, 50, title = "Error Message") + self.d.msgbox(text = "\nSelect at least one option.", height = 7, width = 50, title = "Error Message") else: return tag_checklist elif code_checklist == self.d.CANCEL: @@ -286,11 +286,12 @@ def getDataCheckList(self, text, options, title): self -- An instantiated object of the FormDialogs class. initial_path -- Initial path in the interface. title -- Title displayed on the interface. + extension_file -- Allowed file extension. Return: tag_fselect -- Path of the selected file. """ - def getFile(self, initial_path, title): + def getFile(self, initial_path, title, extension_file): while True: code_fselect, tag_fselect = self.d.fselect(filepath = initial_path, height = 8, @@ -298,11 +299,11 @@ def getFile(self, initial_path, title): title = title) if code_fselect == self.d.OK: if tag_fselect == "": - self.d.msgbox("\nSelect a file. Required value (PEM file).", 7, 50, title = "Error Message") + self.d.msgbox(text = "\nSelect a file. Required value: " + extension_file + " file.", height = 7, width = 50, title = "Error Message") else: ext_file = Path(tag_fselect).suffix - if not ext_file == ".pem": - self.d.msgbox("\nSelect a file. Required value (PEM file).", 7, 50, title = "Error Message") + if not ext_file == extension_file: + self.d.msgbox(text = "\nSelect a file. Required value: " + extension_file + " file.", height = 7, width = 50, title = "Error Message") else: return tag_fselect elif code_fselect == self.d.CANCEL: @@ -327,7 +328,7 @@ def getDirectory(self, initial_path, title): title = title) if code_dselect == self.d.OK: if tag_dselect == "": - self.d.msgbox("\nSelect a directory. Required value (not empty).", 7, 50, title = "Error Message") + self.d.msgbox(text = "\nSelect a directory. Required value (not empty).", height = 7, width = 50, title = "Error Message") else: return tag_dselect elif code_dselect == self.d.CANCEL: @@ -381,7 +382,7 @@ def defineConfiguration(self): def createRepository(self): try: if not path.exists(self.path_conf_file): - self.d.msgbox("\nConfiguration file not found.", 7, 50, title = "Error Message") + self.d.msgbox(text = "\nConfiguration file not found.", height = 7, width = 50, title = "Error Message") else: repository_name = self.getDataInputText("Enter the name to be assigned to the repository:", "repository_name") path_repository = self.getDirectory("/etc/Snap-Tool", "Repository path") @@ -396,12 +397,12 @@ def createRepository(self): message_create_repository = self.telegram.getMessageCreateRepository(repository_name, path_repository, compress_repository) self.telegram.sendTelegramAlert(self.utils.decryptAES(snap_tool_conf['telegram_chat_id']).decode('utf-8'), self.utils.decryptAES(snap_tool_conf['telegram_bot_token']).decode('utf-8'), message_create_repository) self.logger.createSnapToolLog("Repository created: " + repository_name, 1) - self.d.msgbox("\nRepository created: " + repository_name, 7, 50, title = "Notification Message") + self.d.msgbox(text = "\nRepository created: " + repository_name, height = 7, width = 50, title = "Notification Message") conn_es.transport.close() self.mainMenu() except KeyError as exception: - self.logger.createSnapToolLog(exception, 3) - self.d.msgbox("\nFailed to create snapshot. For more information, see the logs.", 8, 50, title = "Error Message") + self.logger.createSnapToolLog("Key Error: " + exception, 3) + self.d.msgbox(text = "\nFailed to create snapshot. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.mainMenu() """ @@ -416,14 +417,14 @@ def createRepository(self): def deleteRepository(self): try: if not path.exists(self.path_conf_file): - self.d.msgbox("\nConfiguration file not found.", 7, 50, title = "Error Message") + self.d.msgbox(text = "\nConfiguration file not found.", height = 7, width = 50, title = "Error Message") else: - snap_tool_conf = self.utils.readYamlFile(self.path_conf_file, 'r') conn_es = self.elastic.getConnectionElastic() list_aux_repositories = self.elastic.getAllRepositories(conn_es) if len(list_aux_repositories) == 0: - self.d.msgbox("\nThere are no repositories created.", 7, 50, title = "Notification Message") + self.d.msgbox(text = "\nThere are no repositories created.", height = 7, width = 50, title = "Notification Message") else: + snap_tool_conf = self.utils.readYamlFile(self.path_conf_file, 'r') list_all_repositories = self.utils.convertListToCheckOrRadioList(list_aux_repositories, "Repository Name") opt_repos = self.getDataCheckList("Select a option:", list_all_repositories, "Repositories") confirm_delete_repos = self.getDataYesOrNo("\nAre you sure to delete the following repository(s)?", "Delete repositories") @@ -440,7 +441,7 @@ def deleteRepository(self): self.mainMenu() except KeyError as exception: self.logger.createSnapToolLog("Key Error: " + exception, 3) - self.d.msgbox("\nFailed to delete repository. For more information, see the logs.", 8, 50, title = "Error Message") + self.d.msgbox(text = "\nFailed to delete repository. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.mainMenu() """ @@ -455,51 +456,57 @@ def deleteRepository(self): def createSnapshot(self): try: if not path.exists(self.path_conf_file): - self.d.msgbox("\nConfiguration file not found.", 7, 50, title = "Error Message") + self.d.msgbox(text = "\nConfiguration file not found.", height = 7, width = 50, title = "Error Message") else: - snap_tool_conf = self.utils.readYamlFile(self.path_conf_file, 'r') conn_es = self.elastic.getConnectionElastic() list_aux_indices = self.elastic.getIndices(conn_es) - list_all_indices = self.utils.convertListToCheckOrRadioList(list_aux_indices, "Index name") - opt_index = self.getDataRadioList("Select a option:", list_all_indices, "Indices") - list_aux_repositories = self.elastic.getAllRepositories(conn_es) - list_all_repositories = self.utils.convertListToCheckOrRadioList(list_aux_repositories, "Repository name") - opt_repo = self.getDataRadioList("Select a option:", list_all_repositories, "Repositories") - self.elastic.createSnapshot(conn_es, opt_repo, opt_index) - message_creation_start = self.telegram.getMessageStartCreationSnapshot(opt_index, opt_repo) - self.telegram.sendTelegramAlert(self.utils.decryptAES(snap_tool_conf['telegram_chat_id']).decode('utf-8'), self.utils.decryptAES(snap_tool_conf['telegram_bot_token']).decode('utf-8'), message_creation_start) - self.logger.createSnapToolLog("Snapshot creation has started: " + opt_index, 1) - while True: - status_snapshot = self.elastic.getStatusSnapshot(conn_es, opt_repo, opt_index) - if status_snapshot == "SUCCESS": - break - sleep(60) - snapshot_info = self.elastic.getSnapshotInfo(conn_es, opt_repo, opt_index) - self.logger.createSnapToolLog("Snapshot creation has finished: " + opt_index, 1) - message_creation_end = self.telegram.getMessageEndSnapshot(opt_index, opt_repo, snapshot_info['snapshots'][0]['start_time'], snapshot_info['snapshots'][0]['end_time']) - self.telegram.sendTelegramAlert(self.utils.decryptAES(snap_tool_conf['telegram_chat_id']).decode('utf-8'), self.utils.decryptAES(snap_tool_conf['telegram_bot_token']).decode('utf-8'), message_creation_end) - self.d.msgbox("\nSnapshot created: " + opt_index, 7, 50, title = "Notification Message") - if snap_tool_conf['is_delete_index'] == True: - self.elastic.deleteIndex(conn_es, opt_index) - if not conn_es.indices.exists(opt_index): - self.logger.createSnapToolLog("Index removed: " + opt_index, 1) - message_delete_index = self.telegram.getMessageDeleteIndex(opt_index) - self.telegram.sendTelegramAlert(self.utils.decryptAES(snap_tool_conf['telegram_chat_id']).decode('utf-8'), self.utils.decryptAES(snap_tool_conf['telegram_bot_token']).decode('utf-8'), message_delete_index) - self.d.msgbox("\nIndex removed: " + opt_index, 7, 50, title = "Notification Message") + if len(list_aux_indices) == 0: + self.d.msgbox(text = "\nThere are no indexes to back up.", height = 7, width = 50, title = "Notification Message") else: - delete_index = self.getDataYesOrNo("\nDo you want to delete the index?\n\n- " + opt_index, "Delete Index") - if delete_index == "ok": - self.elastic.deleteIndex(conn_es, opt_index) - if not conn_es.indices.exists(opt_index): - self.logger.createSnapToolLog("Index removed: " + opt_index, 1) - message_delete_index = self.telegram.getMessageDeleteIndex(opt_index) - self.telegram.sendTelegramAlert(self.utils.decryptAES(snap_tool_conf['telegram_chat_id']).decode('utf-8'), self.utils.decryptAES(snap_tool_conf['telegram_bot_token']).decode('utf-8'), message_delete_index) - self.d.msgbox("\nIndex removed: " + opt_index, 7, 50, title = "Notification Message") + list_all_indices = self.utils.convertListToCheckOrRadioList(list_aux_indices, "Index name") + opt_index = self.getDataRadioList("Select a option:", list_all_indices, "Indices") + list_aux_repositories = self.elastic.getAllRepositories(conn_es) + if len(list_aux_repositories) == 0: + self.d.msgbox(text = "\nThere are no repositories.", height = 7, width = 50, title = "Notification Message") + else: + snap_tool_conf = self.utils.readYamlFile(self.path_conf_file, 'r') + list_all_repositories = self.utils.convertListToCheckOrRadioList(list_aux_repositories, "Repository name") + opt_repo = self.getDataRadioList("Select a option:", list_all_repositories, "Repositories") + self.elastic.createSnapshot(conn_es, opt_repo, opt_index) + message_creation_start = self.telegram.getMessageStartCreationSnapshot(opt_index, opt_repo) + self.telegram.sendTelegramAlert(self.utils.decryptAES(snap_tool_conf['telegram_chat_id']).decode('utf-8'), self.utils.decryptAES(snap_tool_conf['telegram_bot_token']).decode('utf-8'), message_creation_start) + self.logger.createSnapToolLog("Snapshot creation has started: " + opt_index, 1) + while True: + status_snapshot = self.elastic.getStatusSnapshot(conn_es, opt_repo, opt_index) + if status_snapshot == "SUCCESS": + break + sleep(60) + snapshot_info = self.elastic.getSnapshotInfo(conn_es, opt_repo, opt_index) + self.logger.createSnapToolLog("Snapshot creation has finished: " + opt_index, 1) + message_creation_end = self.telegram.getMessageEndSnapshot(opt_index, opt_repo, snapshot_info['snapshots'][0]['start_time'], snapshot_info['snapshots'][0]['end_time']) + self.telegram.sendTelegramAlert(self.utils.decryptAES(snap_tool_conf['telegram_chat_id']).decode('utf-8'), self.utils.decryptAES(snap_tool_conf['telegram_bot_token']).decode('utf-8'), message_creation_end) + self.d.msgbox(text = "\nSnapshot created: " + opt_index, height = 7, width = 50, title = "Notification Message") + if snap_tool_conf['is_delete_index'] == True: + self.elastic.deleteIndex(conn_es, opt_index) + if not conn_es.indices.exists(opt_index): + self.logger.createSnapToolLog("Index removed: " + opt_index, 1) + message_delete_index = self.telegram.getMessageDeleteIndex(opt_index) + self.telegram.sendTelegramAlert(self.utils.decryptAES(snap_tool_conf['telegram_chat_id']).decode('utf-8'), self.utils.decryptAES(snap_tool_conf['telegram_bot_token']).decode('utf-8'), message_delete_index) + self.d.msgbox(text = "\nIndex removed: " + opt_index, height = 7, width = 50, title = "Notification Message") + else: + delete_index = self.getDataYesOrNo("\nDo you want to delete the index?\n\n- " + opt_index, "Delete Index") + if delete_index == "ok": + self.elastic.deleteIndex(conn_es, opt_index) + if not conn_es.indices.exists(opt_index): + self.logger.createSnapToolLog("Index removed: " + opt_index, 1) + message_delete_index = self.telegram.getMessageDeleteIndex(opt_index) + self.telegram.sendTelegramAlert(self.utils.decryptAES(snap_tool_conf['telegram_chat_id']).decode('utf-8'), self.utils.decryptAES(snap_tool_conf['telegram_bot_token']).decode('utf-8'), message_delete_index) + self.d.msgbox(text = "\nIndex removed: " + opt_index, height = 7, width = 50, title = "Notification Message") conn_es.transport.close() self.mainMenu() except KeyError as exception: - self.logger.createSnapToolLog(exception, 3) - self.d.msgbox("\nFailed to create snapshot. For more information, see the logs.", 8, 50, title = "Error Message") + self.logger.createSnapToolLog("Key Error: " + exception, 3) + self.d.msgbox(text = "\nFailed to create snapshot. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.mainMenu() """ @@ -514,20 +521,20 @@ def createSnapshot(self): def deleteSnapshot(self): try: if not path.exists(self.path_conf_file): - self.d.msgbox("\nConfiguration file not found.", 7, 50, title = "Error Message") + self.d.msgbox(text = "\nConfiguration file not found.", height = 7, width = 50, title = "Error Message") else: - snap_tool_conf = self.utils.readYamlFile(self.path_conf_file, 'r') conn_es = self.elastic.getConnectionElastic() list_aux_repositories = self.elastic.getAllRepositories(conn_es) if len(list_aux_repositories) == 0: - self.d.msgbox("\nThere are no repositories created.", 7, 50, title = "Notification Message") + self.d.msgbox(text = "\nThere are no repositories created.", height = 7, width = 50, title = "Notification Message") else: list_all_repositories = self.utils.convertListToCheckOrRadioList(list_aux_repositories, "Repository Name") opt_repo = self.getDataRadioList("Select a option:", list_all_repositories, "Repositories") list_aux_snapshots = self.elastic.getAllSnapshots(conn_es, opt_repo) if len(list_aux_snapshots) == 0: - self.d.msgbox("\nThere are no snapshots created.", 7, 50, title = "Notification Message") + self.d.msgbox(text = "\nThere are no snapshots created.", height = 7, width = 50, title = "Notification Message") else: + snap_tool_conf = self.utils.readYamlFile(self.path_conf_file, 'r') list_all_snapshots = self.utils.convertListToCheckOrRadioList(list_aux_snapshots, "Snapshot Name") opt_snapshots = self.getDataCheckList("Select one or more options:", list_all_snapshots, "Snapshots") delete_snapshot = self.getDataYesOrNo("\nAre you sure to delete the selected snapshot(s)?", "Delete Snapshot(s)") @@ -540,9 +547,10 @@ def deleteSnapshot(self): message += self.utils.convertListToString(opt_snapshots) self.getScrollBox(message, "Snapshot(s) deleted") conn_es.transport.close() + self.mainMenu() except KeyError as exception: - self.logger.createSnapToolLog(exception, 3) - self.d.msgbox("\nFailed to delete snapshot(s). For more information, see the logs.", 8, 50, title = "Error Message") + self.logger.createSnapToolLog("Key Error: " + exception, 3) + self.d.msgbox(text = "\nFailed to delete snapshot(s). For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.mainMenu() """ @@ -557,32 +565,32 @@ def deleteSnapshot(self): def restoreSnapshot(self): try: if not path.exists(self.path_conf_file): - self.d.msgbox("\nConfiguration file not found.", 7, 50, title = "Error Message") + self.d.msgbox(text = "\nConfiguration file not found.", height = 7, width = 50, title = "Error Message") else: - snap_tool_conf = self.utils.readYamlFile(self.path_conf_file, 'r') conn_es = self.elastic.getConnectionElastic() list_aux_repositories = self.elastic.getAllRepositories(conn_es) if len(list_aux_repositories) == 0: - self.d.msgbox("\nThere are no repositories created.", 7, 50, title = "Notification Message") + self.d.msgbox(text = "\nThere are no repositories created.", height = 7, width = 50, title = "Notification Message") else: list_all_repositories = self.utils.convertListToCheckOrRadioList(list_aux_repositories, "Repository Name") opt_repo = self.getDataRadioList("Select a option:", list_all_repositories, "Repositories") list_aux_snapshots = self.elastic.getAllSnapshots(conn_es, opt_repo) if len(list_aux_snapshots) == 0: - self.d.msgbox("\nThere are no snapshots created.", 7, 50, title = "Notification Message") + self.d.msgbox(text = "\nThere are no snapshots created.", height = 7, width = 50, title = "Notification Message") else: + snap_tool_conf = self.utils.readYamlFile(self.path_conf_file, 'r') list_all_snapshots = self.utils.convertListToCheckOrRadioList(list_aux_snapshots, "Snapshot Name") opt_snapshot = self.getDataRadioList("Select a option:", list_all_snapshots, "Snapshots") self.elastic.restoreSnapshot(conn_es, opt_repo, opt_snapshot) message_restore_snapshot = self.telegram.getMessageRestoreSnapshot(opt_repo, opt_snapshot) self.telegram.sendTelegramAlert(self.utils.decryptAES(snap_tool_conf['telegram_chat_id']).decode('utf-8'), self.utils.decryptAES(snap_tool_conf['telegram_bot_token']).decode('utf-8'), message_restore_snapshot) self.logger.createSnapToolLog("Snapshot restored: " + opt_snapshot, 1) - self.d.msgbox("\nSnapshot restored: " + opt_snapshot + '.', 7, 50, title = "Notification Message") + self.d.msgbox(text = "\nSnapshot restored: " + opt_snapshot + '.', height = 7, width = 50, title = "Notification Message") conn_es.transport.close() - self.mainMenu() + self.mainMenu() except KeyError as exception: - self.logger.createSnapToolLog(exception, 3) - self.d.msgbox("\nFailed to restore snapshot. For more information, see the logs.", 8, 50, title = "Error Message") + self.logger.createSnapToolLog("Key Error: " + exception, 3) + self.d.msgbox(text = "\nFailed to restore snapshot. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.mainMenu() """ @@ -597,32 +605,32 @@ def restoreSnapshot(self): def mountSearchableSnapshot(self): try: if not path.exists(self.path_conf_file): - self.d.msgbox("\nConfiguration file not found.", 7, 50, title = "Error Message") + self.d.msgbox(text = "\nConfiguration file not found.", height = 7, width = 50, title = "Error Message") else: - snap_tool_conf = self.utils.readYamlFile(self.path_conf_file, 'r') conn_es = self.elastic.getConnectionElastic() list_aux_repositories = self.elastic.getAllRepositories(conn_es) if len(list_aux_repositories) == 0: - self.d.msgbox("\nThere are no repositories created.", 7, 50, title = "Notification Message") + self.d.msgbox(text = "\nThere are no repositories created.", height = 7, width = 50, title = "Notification Message") else: list_all_repositories = self.utils.convertListToCheckOrRadioList(list_aux_repositories, "Repository Name") opt_repo = self.getDataRadioList("Select a option:", list_all_repositories, "Repositories") list_aux_snapshots = self.elastic.getAllSnapshots(conn_es, opt_repo) if len(list_aux_snapshots) == 0: - self.d.msgbox("\nThere are no snapshots created.", 7, 50, title = "Notification Message") + self.d.msgbox(text = "\nThere are no snapshots created.", height = 7, width = 50, title = "Notification Message") else: + snap_tool_conf = self.utils.readYamlFile(self.path_conf_file, 'r') list_all_snapshots = self.utils.convertListToCheckOrRadioList(list_aux_snapshots, "Snapshot Name") opt_snapshot = self.getDataRadioList("Select a option:", list_all_snapshots, "Snapshots") self.elastic.mountSearchableSnapshot(conn_es, opt_repo, opt_snapshot) message_searchable_snapshot = self.telegram.getMessageSearchableSnapshot(opt_repo, opt_snapshot) self.telegram.sendTelegramAlert(self.utils.decryptAES(snap_tool_conf['telegram_chat_id']).decode('utf-8'), self.utils.decryptAES(snap_tool_conf['telegram_bot_token']).decode('utf-8'), message_searchable_snapshot) self.logger.createSnapToolLog("Snapshot mounted as searchable snapshot: " + opt_snapshot, 1) - self.d.msgbox("\nSnapshot mounted as searchable snapshot: " + opt_snapshot + '.', 8, 50, title = "Notification Message") + self.d.msgbox(text = "\nSnapshot mounted as searchable snapshot: " + opt_snapshot + '.', height = 8, width = 50, title = "Notification Message") conn_es.transport.close() - self.mainMenu() + self.mainMenu() except KeyError as exception: - self.logger.createSnapToolLog(exception, 3) - self.d.msgbox("\nFailed to mount snapshot as a searchable snapshot. For more information, see the logs.", 8, 50, title = "Error Message") + self.logger.createSnapToolLog("Key Error: " + exception, 3) + self.d.msgbox(text = "\nFailed to mount snapshot as a searchable snapshot. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.mainMenu() """ @@ -637,7 +645,7 @@ def mountSearchableSnapshot(self): def deleteIndices(self): try: if not path.exists(self.path_conf_file): - self.d.msgbox("\nConfiguration file not found.", 7, 50, title = "Error Message") + self.d.msgbox(text = "\nConfiguration file not found.", height = 7, width = 50, title = "Error Message") else: conn_es = self.elastic.getConnectionElastic() list_aux_indices = self.elastic.getIndices(conn_es) @@ -661,7 +669,7 @@ def deleteIndices(self): self.mainMenu() except KeyError as exception: self.logger.createSnapToolLog("Key Error: " + exception, 3) - self.d.msgbox("\nFailed to delete indices. For more information, see the logs.", 8, 50, title = "Error Message") + self.d.msgbox(text = "\nFailed to delete indices. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.mainMenu() """ @@ -671,17 +679,17 @@ def deleteIndices(self): self -- An instantiated object of the FormDialog class. """ def showNodesDiskSpace(self): - message = "Occupied space in nodes:\n\n" + message_to_display = "Occupied space in nodes:\n\n" conn_es = self.elastic.getConnectionElastic() nodes_info = self.elastic.getNodesInformation(conn_es) for node in nodes_info: - message += "- " + nodes_info[node]['name'] + "\n" + message_to_display += "- " + nodes_info[node]['name'] + '\n' total_disk = nodes_info[node]['fs']['total']['total_in_bytes'] available_disk = nodes_info[node]['fs']['total']['available_in_bytes'] percentage = 100 - (available_disk * 100 / total_disk) - message += "Percent occupied on disk: " + str(round(percentage, 2)) + "%\n\n" + message_to_display += "Percent occupied on disk: " + str(round(percentage, 2)) + "%\n\n" conn_es.transport.close() - self.getScrollBox(message, "Node Information") + self.getScrollBox(message_to_display, "Node Information") self.mainMenu() """ @@ -719,34 +727,34 @@ def switchMmenu(self, option): exit(0) """ - Method that launches an action based on the option chosen in the Snapshots menu. + Method that launches an action based on the option chosen in the Repositories menu. Parameters: self -- An instantiated object of the FormDialog class. option -- Chosen option. """ - def switchSmenu(self, option): + def switchRmenu(self, option): if option == 1: - self.createSnapshot() + self.createRepository() elif option == 2: self.deleteSnapshot() - elif option == 3: - self.restoreSnapshot() - elif option == 4: - self.mountSearchableSnapshot() """ - Method that launches an action based on the option chosen in the Repositories menu. + Method that launches an action based on the option chosen in the Snapshots menu. Parameters: self -- An instantiated object of the FormDialog class. option -- Chosen option. """ - def switchRmenu(self, option): + def switchSmenu(self, option): if option == 1: - self.createRepository() + self.createSnapshot() elif option == 2: self.deleteSnapshot() + elif option == 3: + self.restoreSnapshot() + elif option == 4: + self.mountSearchableSnapshot() """ Method that launches an action based on the option chosen in the Indices menu. @@ -760,19 +768,22 @@ def switchImenu(self, option): self.deleteIndices() """ - Method that defines the menu of actions that can be performed in relation to snaphosts. + Method that defines the menu on the actions to be carried out in the main menu. Parameters: self -- An instantiated object of the FormDialog class. """ - def snapshotMenu(self): - options_sm = [("1", "Create Snapshot"), - ("2", "Delete Snapshot(s)"), - ("3", "Restore snapshot"), - ("4", "Mount searchable snapshot")] + def mainMenu(self): + options_mm = [("1", "Snap-Tool Configuration"), + ("2", "Repositories"), + ("3", "Snapshots"), + ("4", "Indices"), + ("5", "Nodes information"), + ("6", "About"), + ("7", "Exit")] - option_sm = self.getMenu("Select a option:", options_sm, "Snapshots Menu") - self.switchSmenu(int(option_sm)) + option_mm = self.getMenu("Select a option:", options_mm, "Main Menu") + self.switchMmenu(int(option_mm)) """ Method that defines the menu of actions that can be performed in relation to repositories. @@ -788,31 +799,28 @@ def repositoryMenu(self): self.switchRmenu(int(option_rm)) """ - Method that defines the menu of actions that can be performed in relation to indices. + Method that defines the menu of actions that can be performed in relation to snaphosts. Parameters: self -- An instantiated object of the FormDialog class. """ - def indicesMenu(self): - options_im = [("1", "Delete indices")] + def snapshotMenu(self): + options_sm = [("1", "Create Snapshot"), + ("2", "Delete Snapshot(s)"), + ("3", "Restore snapshot"), + ("4", "Mount searchable snapshot")] - option_im = self.getMenu("Select a option:", options_im, "Indices Menu") - self.switchImenu(int(option_im)) + option_sm = self.getMenu("Select a option:", options_sm, "Snapshots Menu") + self.switchSmenu(int(option_sm)) """ - Method that defines the menu on the actions to be carried out in the main menu. + Method that defines the menu of actions that can be performed in relation to indices. Parameters: self -- An instantiated object of the FormDialog class. """ - def mainMenu(self): - options_mm = [("1", "Snap-Tool Configuration"), - ("2", "Repositories"), - ("3", "Snapshots"), - ("4", "Indices"), - ("5", "Nodes information"), - ("6", "About"), - ("7", "Exit")] + def indicesMenu(self): + options_im = [("1", "Delete indices")] - option_mm = self.getMenu("Select a option:", options_mm, "Main Menu") - self.switchMmenu(int(option_mm)) \ No newline at end of file + option_im = self.getMenu("Select a option:", options_im, "Indices Menu") + self.switchImenu(int(option_im)) \ No newline at end of file diff --git a/Snap-Tool/modules/LoggerClass.py b/Snap-Tool/modules/LoggerClass.py index 9d22ac8..ecfba45 100644 --- a/Snap-Tool/modules/LoggerClass.py +++ b/Snap-Tool/modules/LoggerClass.py @@ -2,13 +2,11 @@ from logging import getLogger, INFO, Formatter, FileHandler """ -Class that manages everything related to the application's -records. +Class that manages everything related to the application's records. """ class Logger: """ - Method that writes the logs generated by the application - in a file. + Method that writes the logs generated by the application in a file. Parameters: self -- An instantiated object of the Logger class. diff --git a/Snap-Tool/modules/TelegramClass.py b/Snap-Tool/modules/TelegramClass.py index cd0078f..3cb5956 100644 --- a/Snap-Tool/modules/TelegramClass.py +++ b/Snap-Tool/modules/TelegramClass.py @@ -63,7 +63,7 @@ def sendTelegramAlert(self, telegram_chat_id, telegram_bot_token, message): header -- Header of the message. """ def getHeaderMessage(self): - header = u'\u26A0\uFE0F' + " " + 'Snap-Tool' + " " + u'\u26A0\uFE0F' + '\n\n' + u'\u23F0' + " Alert sent: " + strftime("%c") + "\n\n\n" + header = u'\u26A0\uFE0F' + " " + 'Snap-Tool' + " " + u'\u26A0\uFE0F' + "\n\n" + u'\u23F0' + " Alert sent: " + strftime("%c") + "\n\n\n" return header """ @@ -115,12 +115,12 @@ def getMessageDeleteRepository(self, repository_name): def getMessageDeleteSnapshot(self, snapshot_name): message = self.getHeaderMessage() message += u'\u2611\uFE0F' + " Action: Snaphot removed\n" - message += u'\u2611\uFE0F' + " Snapshot name: " + snapshot_name +"\n" + message += u'\u2611\uFE0F' + " Snapshot name: " + snapshot_name + '\n' message += u'\u2611\uFE0F' + " Index name: " + snapshot_name return message """ - Method that generates the message in Telegram when a snapshot is restored. + Method that generates the message in Telegram for when a snapshot is restored. Parameters: self -- An instantiated object of the Telegram class. @@ -133,12 +133,12 @@ def getMessageDeleteSnapshot(self, snapshot_name): def getMessageRestoreSnapshot(self, repository_name, snapshot_name): message = self.getHeaderMessage() message += u'\u2611\uFE0F' + " Action: Snapshot restore\n" - message += u'\u2611\uFE0F' + " Snapshot name: " + snapshot_name +"\n" + message += u'\u2611\uFE0F' + " Snapshot name: " + snapshot_name + '\n' message += u'\u2611\uFE0F' + " Repository name: " + repository_name return message """ - Method that generates the message in Telegram when a snapshot is mounted as a searchable snapshot. + Method that generates the message in Telegram for when a snapshot is mounted as a searchable snapshot. Parameters: self -- An instantiated object of the Telegram class. @@ -151,7 +151,7 @@ def getMessageRestoreSnapshot(self, repository_name, snapshot_name): def getMessageSearchableSnapshot(self, repository_name, snapshot_name): message = self.getHeaderMessage() message += u'\u2611\uFE0F' + " Action: Snapshot mounted as a searchable snapshot\n" - message += u'\u2611\uFE0F' + " Snapshot name: " + snapshot_name +"\n" + message += u'\u2611\uFE0F' + " Snapshot name: " + snapshot_name + '\n' message += u'\u2611\uFE0F' + " Repository name: " + repository_name return message @@ -169,8 +169,8 @@ def getMessageSearchableSnapshot(self, repository_name, snapshot_name): def getMessageStartCreationSnapshot(self, index_name, repository_name): message = self.getHeaderMessage() message += u'\u2611\uFE0F' + " Action: Snapshot creation has started\n" - message += u'\u2611\uFE0F' + " Snapshot name: " + index_name +"\n" - message += u'\u2611\uFE0F' + " Index name: " + index_name + "\n" + message += u'\u2611\uFE0F' + " Snapshot name: " + index_name + '\n' + message += u'\u2611\uFE0F' + " Index name: " + index_name + '\n' message += u'\u2611\uFE0F' + " Repository name: " + repository_name return message @@ -190,9 +190,9 @@ def getMessageStartCreationSnapshot(self, index_name, repository_name): def getMessageEndSnapshot(self, snapshot_name, repository_name, start_time, end_time): message = self.getHeaderMessage() message += u'\u2611\uFE0F' + " Action: Snapshot creation completed\n" - message += u'\u2611\uFE0F' + " Snapshot name: " + snapshot_name +"\n" - message += u'\u2611\uFE0F' + " Repository name: " + repository_name + "\n" - message += u'\u2611\uFE0F' + " Start time: " + str(start_time) + "\n" + message += u'\u2611\uFE0F' + " Snapshot name: " + snapshot_name + '\n' + message += u'\u2611\uFE0F' + " Repository name: " + repository_name + '\n' + message += u'\u2611\uFE0F' + " Start time: " + str(start_time) + '\n' message += u'\u2611\uFE0F' + " End time: " + str(end_time) return message diff --git a/Snap-Tool/modules/UtilsClass.py b/Snap-Tool/modules/UtilsClass.py index cee31b7..fe91a28 100644 --- a/Snap-Tool/modules/UtilsClass.py +++ b/Snap-Tool/modules/UtilsClass.py @@ -56,7 +56,7 @@ def createYamlFile(self, data, path_file_yaml, mode): safe_dump(data, file_yaml, default_flow_style = False) except IOError as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nError creating YAML file. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nError creating YAML file. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() """ @@ -79,7 +79,7 @@ def readYamlFile(self, path_file_yaml, mode): data_file_yaml = safe_load(file_yaml) except IOError as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nError opening or reading the YAML file. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nError opening or reading the YAML file. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() else: return data_file_yaml @@ -104,7 +104,7 @@ def getPathSnapTool(self, path_dir): path_final = path.join(path_main, path_dir) except (OSError, TypeError) as exception: self.logger.createLogSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nAn error has occurred. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nAn error has occurred. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() else: return path_final @@ -128,7 +128,7 @@ def getPassphrase(self): file_key.close() except FileNotFoundError as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nError opening or reading the Key file. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nError opening or reading the Key file. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() else: return pass_key @@ -203,7 +203,7 @@ def getHashToFile(self, path_file): hash_sha.update(block) except IOError as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nError getting the file's hash function. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nError getting the file's hash function. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() else: return hash_sha.hexdigest() @@ -219,7 +219,7 @@ def getHashToFile(self, path_file): Encrypted text. Exceptions: - binascii.Error -- Is raised if were incorrectly padded or if there are non-alphabet characters present in the string. + Exception -- It is thrown when any exception is found. """ def encryptAES(self, text): try: @@ -229,7 +229,7 @@ def encryptAES(self, text): aes = AES.new(key, AES.MODE_CBC, IV) except Exception as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to encrypt the data. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to encrypt the data. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() else: return b64encode(IV + aes.encrypt(pad(text_bytes, AES.block_size))) @@ -255,7 +255,7 @@ def decryptAES(self, text_encrypt): aes = AES.new(key, AES.MODE_CBC, IV) except binascii.Error as exception: self.logger.createSnapToolLog(exception, 3) - self.form_dialog.d.msgbox("\nFailed to decrypt the data. For more information, see the logs.", 8, 50, title = "Error Message") + self.form_dialog.d.msgbox(text = "\nFailed to decrypt the data. For more information, see the logs.", height = 8, width = 50, title = "Error Message") self.form_dialog.mainMenu() else: return unpad(aes.decrypt(text_encrypt[AES.block_size:]), AES.block_size) \ No newline at end of file diff --git a/Snap-Tool/modules/__pycache__/ConfigurationClass.cpython-36.pyc b/Snap-Tool/modules/__pycache__/ConfigurationClass.cpython-36.pyc index 2ccdbbb70eb865722785961efb46118b6532bd3e..f1c0d90c5bae7a078aa790753fc4087ad3f27732 100644 GIT binary patch delta 2363 zcmZ8hNlaT;6#eh}d-}G)fNk&q*k%X@N+4u{K*&G_XhS9f2_Xq2*I+)0Nj`KCeU2k} zQ>CpG=`AWXQI>5~RUJYXQI$sOBCD#Z)I}GlQrl(J4VBt1vZ#9R!|F%30Jzwj0R2C5LtH8byYid1vB>c8O9?A>0oRc#{<<0_Q%B^R_ z!B0G^Z*r~}ieZNes4S>C7k7bsUcJZ64(ZUzjSi>bl0G+7L7<=(Sk5hz-G&aq0(-^f zfe_@YrA;>C`ljub)|&|bFGc7uKcBSGsm*laWj2mrO~m*LfWdflzwMv)sach&1RNX?QhS)Qf4LS`hl@k z@rU~}<`*C6#+g=!qQH5$026Q#CgBoXhAXqK9D^yCo>g-yT%Bb(8(agxl?G{C*k^gp+otz^D)2 zq(Qnd*W#{(!!Rh_&Ahqf9>8;C*Uv5V@~XVG!kWL?)ta->wU`G-9;mq%`Pf_NvC9{P zH|74~d7fF$H9yf zWXCjRDY6uuQRnr04zrOdxelf*?_AH}9Wc5j=Nyb-w{aCtib z>k@szZoB;H5ZT~GUxlJ5Db?)45A&M%B{-#3A{E~|_LZ4c>w9V1n-PUj1KTY=4Yj-X zkg%6*-#NqLpU`$kfRvzk>5SB9W{4Km1T_RxNE!XWV@7U8I(#1-?HMLAA< z$RZ4jr!|TE2q`D6GVxOu;WY6x7D1MrwFtw+M=ioR;$s#;mW*43^TaPGN>}MIOptQX zVv(tn7D0ww5)0nSFSB&XUx~g&_S?W{2JZHb_gxji_u~HQx@YC5ScpXfOjrF(XD;T)e{YeOq2hmB_an<+CU1lwgOJk9DdN)vR|z^1Mqg-% zFYE}Vsii%dUmO@9zCN$SXo>3jAxNO1HD^h+n>UCr5V#3$RSiE}Ea*bINK@~!X-u=rKeu?0$T9o>4tgQ% delta 2310 zcmZ8iOKe+36n*pj|HgLwiDNtdIZmEKGogBF?CBNKZzcWfyihgRwGN|$$wi;j_iBp$=Zec)=5=Xkhzju)jx z*_s&rOP(j71tA4(kcJFo-?qxLhO}nLL3=~Rdx{#a4(LQzm*G;ha6@4VD3_FCepGxE z&WWF_-M(?x;?i_waeireWi>B84!5kuY)x#fWPi@w?1^no5T~;O3mO#$Xbr=G2-Bl{r?kz%7`@$O?dw4Q5~# zBRkAtA9f*I!TOo|GMW@xh z#;PXrzP@?~Pe5F{AIV$rqGum^4&VhRd6J^CQavu$Jg|q`a1t!F6i-2PJty>1)-uhH zqIsczeJAYW$puzx;}Olr+vGIP8aXV`hdI(b4bg+9B)d0~_+bExsI?5w!1}tnEqO0b z!XXj&XGiW~@$$xzth(DMVFx#s$nq?NWQ7Uz4Gt=ZhX)ndPrL9~k8nMO>sI}qX!yJ9 z51>%XRsB2+Il1dijN9*9?!I|N;W;Qs?FrUUwI*3907u}coYYRd*!KeNtMP4*oJc;{*8~gvE9d+aUr;G%rw;uX>TSmOk#L+ zX=Pz{VY;%iuvE>QompP4%*_;;9uf7}C-oRve8alFq#J7%eIM=KPrwk07X1M6ev>dj z{GdrVM7(Sg28kaw2}g(@H3`QQ{0pfcHz_BG51E8v;wMeQDdMM1!U*v*CSh3o(2^aN z@~BxRe$FJECw{>s$SoI5!X=T9XJfL3D<)a`u8Mc!Bc2wLTL}{4k9a1RLi)u1>K&-8 z%;-td+X&JG8G@`Rv}Ph5L^}x_1YHFAwHvMPu}lH^xPQK~JU@v^DwESo)mgnrh8+am zBAm>#9#Kvn#EE$=IcDEQ@^0~4vd7*_bkCY26=jJKbE^UTzv5(m{Ph~#&HQWqZ9lX6 zH8MvDrV)x({W|dpf*S-Sgv-yYh|g>Y#z4{=GT}Kl_ diff --git a/Snap-Tool/modules/__pycache__/ElasticClass.cpython-36.pyc b/Snap-Tool/modules/__pycache__/ElasticClass.cpython-36.pyc index c6362ca46dc4614039b0c0cf037875da1d218ab0..8270de824d46d182b43a402553422606b7a2a08b 100644 GIT binary patch delta 2839 zcmb_eOKcle6rE>}$IfIX9)Blp5+`;biPNfqv?_Apb!(MrVs>&b#crtfpM9RZqAgfyj|jRj(D_W%)t$1JSFlTK@fN^K_5W|A)`0}tC*Xyrb;$!RuhLF zqALRk*_P*du4v7axV@e866YFSSw|2ec$i=i!3mtm<%=U`v7C{3oOUX;Wq+C6C|TL9FjK%`DXglCkP%#fU9!( z&E=JQP9tE^wNYuwReGXYs;Ze4Z7A0-N{DdktoI7Q7ebM20s+Kq~k#0^Vu_0u%#7 zti=FI3Pqp=e$r*B5(M~Nw^tCaxZ)kjFsIF$cvtCa^L&R~wPtA7cYKH*tuU60hd*KS|+xiF-|e zt3u$d0%&?fyV{2JJH-j;18fBR4sKfx&$vN=mSl!nfW#C^j$6BNS2+m z%hr7m_up2#t^=}i<-D<6dDaQKpCKP=#%@{;Js9p`$ex-rH8hQp6+Q_)8b65}bJ4~^ zUhtd}VL;Jcqx%{J`dp)rg!QJnQ{Y|g?lyP4$4vV^1U8I_N>?U}JBuZoFHV-Kjt8F} zguRiVwv`mNR27oGLL>!UZ87#X47c^C4r0spV!QShe3PDPtM)d?^FqHo0DpHz;ab}$ znoes^vLi6ierd%dy4HJAYo+<)){{unFfUOsk=sVXz|> z&5}5Bp}<5;pu*y;6<+RWk9%nkUrSv2A$$eWhazL3d|Q`~vBS z)^)Tn*-iCW=VPsnL)RU+%YUN07U6*o%BM69tqP?dXiHE@OlF#7XzlcQ zf|}MT5ujFxgpaNel_~^ufe^5%D#Vf?!J-lpY!H%#5M_Y`3l@kSEV$P*PVA(CM8eTK z$-H@)Ip==P^$!QG?pL>l!|Getr}DoJTvzW>7`t&md7DAIX24HT4RVnvNU<)soY?|% zdIYXTy3!SuTNB07xKrd)6{S?jPn5=rPQ~|Z(VT+sBa>r{cL{=!3-Mn3H7Jj8EALob z#S4Bn?Szh4XD6+avzPNCuY?a4oq^(H!K_|#jgI9AnhCZLbR$%hGx_ntV^(1@=jUVDZR{W!DeOA zWOIxO$A!WPpWukLgv)nId`{&y6Q(65oZnU!Y`-m+B~yG!WCC6VY{hOeCDUj6P1y{< zhuKy*6A!cPa5H2Ld(4Awkef~GwB=Ysi>Rk{T4w`|$#ueH)88NrC7FuZWU6yMPp%4DqEoIW zs2BPY7hh=5SdmLk-kB_|O;})wuu8aAu+@?oRNaRTiu=<{VS!lPQAWvLH(|Eqo*3xOQV*vHt9>Nlhg`HTVBj} zldal8Dq%O@Fdb=u)`SZ0v_&;H+xP}PK+p^GZTXX)@^H1b`J;HFBIhS2tb&O}P~?tf zt}PM`MF6k2Zq5KwR*}~kJyWad z4|MbChP)02I67gGd;YlEeyv#`$sh-+=GGH58`ZQlt7}?H<|ol@&p3E+DqeFWbQ)%g zbLc&{Dc`~}nNmaZdY)t|b>%ywr?jSnW|w8~`DN1n+m}!pSTWsLtX7&Jd`#AIC?ons?3k;Ny-hZsWPL`Hzlh zoD=yNv8$cP#*pnM_%qcZ@(19%?Cdd5BYuOzmks)9;CKUVZ9}@E7K)}-8mjZ~AJ@mUS4dsW z!znFdXQwzK!9&>?{B2ys>bMXeKJJkSxpna*GFjS;n`v+L-xzk$af)#}dk^w0Iq2#< z`S4cSyq}<-U@yS|f`bG{2#yjQBRD}YL|_t(5{$uDoqfqilorKM5=si!IwgibhLXg+ J7D(LrK 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 delta 562 zcmajcO-lk%6b4`~jiWPujZFnGo?%0&>S(Ff5|37Kk&0kybJUj%oA zZ!=gXxJWejFxVooVS>;gfx+G+ef-D8EGegX)4JRyq9-_3*I`egek{*ddx4hK>6qsh zEw~Ti3L#6(i@a_OM~A6^jJVJfY{#OWcy-CMTK}EOKB6lT4ZSKyC$!`iW4D8gUX&c) z@~LEh30sP%$SSLIHTNdoBjzxSItUZT)H2utGVp^csDN!wEqH9V&CZE!d;T(|7xa&h nVV1g}?4WZO>;a8Es`twol=Hv<3dn&XSOc429~`*#u>|=7WY}wB