From 9f8723f2c70d331c4b54ad7446767ae86491d8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=81?= Date: Wed, 20 May 2020 18:10:00 +0300 Subject: [PATCH] ._cfg logic is done. Some append methods almost written. --- template_action_draft.py | 409 ++++++++++++++++++++++++++++++--------- 1 file changed, 313 insertions(+), 96 deletions(-) diff --git a/template_action_draft.py b/template_action_draft.py index b6208fc..50d2a92 100644 --- a/template_action_draft.py +++ b/template_action_draft.py @@ -4,7 +4,10 @@ from calculate.templates.template_processor import TemplateAction from calculate.utils.package import PackageAtomParser, Package, PackageNotFound from calculate.utils.files import write_file, read_link, read_file_lines,\ FilesError, join_paths +from calculate.utils.mount import Mounts from collections import OrderedDict +import hashlib +import stat import glob import shutil import os @@ -83,6 +86,7 @@ class CalculateConfigFile: "cannot read calculate config file in: {0}. Reason: {1}". format(self.cl_config_path, str(error))) + # Продумать проверку корректности найденного файла. for file_line in config_file_lines: filename, md5_sum = file_line.split(' ') config_dictionary.update({filename: md5_sum}) @@ -166,12 +170,18 @@ class TemplateWrapper: # Флаг, разрешающий изменение хэш-суммы в contents после отработки # шаблона. - self.change_md5 = True + self.update_contents = False + self.update_config = False + self.update_archive = False # Флаг, указывающий, что нужно удалить все имеющиеся ._cfg????_filename # файлы. self.clean_cfg = False + self.clean_config = False + self.check_config = False + + # Получаем класс соответствующего формата файла. if self.parameters.format: self.format_class = ParametersProcessor.\ available_formats[self.parameters.format] @@ -201,7 +211,9 @@ class TemplateWrapper: "._cfg????_{}".format( os.path.basename(self.target_path))) - self._cfg_list = glob.glob(self._cfg_pattern) + self.cfg_list = glob.glob(self._cfg_pattern) + + self.check_user_changes() def check_conflicts(self): '''Проверка конфликтов типов.''' @@ -289,39 +301,79 @@ class TemplateWrapper: return target_md5 = self.target_package.get_md5(self.target_path) + md5_comparision_result = self.target_package.is_md5_equal( + self.target_path, + file_md5=target_md5) - if (self.parameters.autoupdate or - self.target_package.is_md5_equal(self.target_path, - file_md5=target_md5)): - if not self._cfg_list: + if (self.parameters.autoupdate or md5_comparision_result): + if not self.cfg_list: # Приоритет отдаем пути из параметра source. if self.parameters.source: self.input_path = self.parameters.source else: self.input_path = self.target_path - self.output_path = self.target_path - self.md5_from = self.target_path - # Должна быть какая-то проверка userspace. - self.update_archive = True + + # Чем обновляем CONTENTS + self.update_contents = self.target_path + + # Обновляем CA + # Должна быть какая-то проверка на предмет userspace. + self.update_archive = self._get_archive_path(self.target_path) + + # Очищаем CL + self.clean_config = True else: - self.clean_cfg = True + # Приоритет отдаем пути из параметра source. if self.parameters.source: self.input_path = self.parameters.source else: self.input_path = self.target_path self.output_path = self.target_path - self.md5_from = self.target_path - # Должна быть какая-то проверка userspace. - self.update_archive = True + + # Чем обновляем CONTENTS + self.update_contents = self.target_path + + # Обновляем CA + # Должна быть какая-то проверка на предмет userspace. + self.update_archive = self._get_archive_path(self.target_path) + + # Очищаем CL + self.clean_config = True + + # Убираем имеющиеся ._cfg????_filename + self.clean_cfg = True else: - if not self._cfg_list: + if not self.cfg_list: + # Приоритет отдаем пути из параметра source. if self.parameters.source: self.input_path = self.parameters.source else: self.input_path = self._get_archive_path(self.target_path) + self.output_path = self._get_cfg_path(self.target_path) + + # Чем обновляем CONTENTS + self.update_contents = self.output_path + + # Oбновляем хэш-сумму в CL + self.update_config = self.output_path + + self.check_config = True else: - pass + # Приоритет отдаем пути из параметра source. + if self.parameters.source: + self.input_path = self.parameters.source + else: + self.input_path = self._get_archive_path(self.target_path) + self.output_path = self._get_cfg_path(self.target_path) + + # Чем обновляем CONTENTS + self.update_contents = self.output_path + + # Обновляем хэш-сумму в CL + self.update_config = self.output_path + + self.check_config = True def _get_archive_path(self, file_path): if self.chroot_path != "/" and file_path.startswith(self.chroot_path): @@ -329,7 +381,23 @@ class TemplateWrapper: return join_paths(self.config_archive_path, file_path) def _get_cfg_path(self, file_path): - pass + if self.cfg_list: + last_cfg_name = os.path.basename(self.cfg_list[-1]) + + slice_value = len('._cfg') + cfg_number = int(last_cfg_name[slice_value: slice_value + 4]) + else: + cfg_number = 0 + + cfg_number = str(cfg_number + 1) + + if len(cfg_number) < 4: + cfg_number = '0' * (4 - len(cfg_number)) + cfg_number + new_cfg_name = "._cfg{}_{}".format(cfg_number, + os.path.basename(file_path)) + new_cfg_path = os.path.join(os.path.dirname(file_path), new_cfg_name) + + return new_cfg_path def _update_contents(self): pass @@ -392,6 +460,7 @@ class TemplateExecutor: self.directory_appends = {'join': self._append_join_directory, 'remove': self._append_remove_directory, 'clear': self._append_clear_directory} + self.file_appends = {'join': self._append_join_file} self.formats_classes = ParametersProcessor.available_formats @@ -402,26 +471,83 @@ class TemplateExecutor: cl_config_path=cl_config_path, cl_chroot_path=chroot_path) - def use_template(self, target_path, parameters, template_type, - template_text=''): + @property + def available_appends(self): + appends_set = set(self.directory_appends.keys()).union( + set(self.file_appends.keys())) + + return appends_set + + def execute_template(self, target_path, parameters, template_type, + template_text=''): print('Template parameters:') parameters.print_parameters_for_debug() - try: - template_object = TemplateWrapper(target_path, parameters, - template_type, - template_text=template_text) - except TemplateTypeConflict as error: - pass + # try: + template_object = TemplateWrapper(target_path, parameters, + template_type, + template_text=template_text) + # except TemplateTypeConflict as error: + # pass + if template_object.remove_original: + if template_object.target_type == DIR: + self._remove_directory(template_object.target_path) + else: + self._remove_file(template_object.target_path) + + # Добавить поддержку run, excute и т.д. self.directory_appends[template_object.parameters.append]( template_object) - def _create_directory(self, template_object): + def _append_join_directory(self, template_object: TemplateWrapper): + pass + + def _append_remove_directory(self, template_object: TemplateWrapper): + pass + + def _append_clear_directory(self, template_object: TemplateWrapper): + pass + + def _append_join_file(self, template_object: TemplateWrapper): + input_path = template_object.input_path + output_path = template_object.output_path + + template_format = template_object.format_class + + with open(input_path, 'r') as input_file: + input_text = input_file.read() + parsed_input = template_format(input_text) + + parsed_template = template_format(template_object.template_text) + parsed_input.join_template(parsed_template) + + output_text = parsed_input.document_text + changed_file_md5 = hashlib.md5(output_text.encode()).hexdigest() + + with open(output_path, 'w') as output_file: + output_file.write(parsed_input.document_text) + + if template_object.clean_cfg: + for cfg_file_path in template_object.cfg_list: + self._remove_file(cfg_file_path) + + if template_object.update_archive: + with open(template_object.update_archive, 'r') as ca_file: + ca_text = ca_file.read() + parsed_ca = template_format(ca_text) + + parsed_ca.join_template(parsed_template) + ca_text = parsed_ca.document_text + + template_object._update_contents() + + def _create_directory(self, template_object: TemplateWrapper): target_path = template_object.target_path - print("append = 'join'") + template_parameters = template_object.parameters + if os.access(target_path, os.F_OK): - if self.template_parameters.chmod: + if template_parameters.chmod: self.chmod_directory(target_path) if self.template_parameters.chown: @@ -451,16 +577,16 @@ class TemplateExecutor: try: os.mkdir(create_path) - if (self.template_parameters.chmod and - self.template_parameters.chmod != current_mod): + if (template_parameters.chmod and + template_parameters.chmod != current_mod): self.chmod_directory(create_path) elif 'chmod' in self.directory_default_parameters: self.chmod_directory( create_path, chmod_value=self.directory_default_parameters['chown']) - if (self.template_parameters.chown and - self.template_parameters.chown != current_owner): + if (template_parameters.chown and + template_parameters.chown != current_owner): self.chown_directory elif 'chown' in self.directory_default_parameters: self.chown_directory( @@ -473,93 +599,184 @@ class TemplateExecutor: format(create_path, str(error))) def _remove_directory(self, target_path): - print("append = 'remove'") - if os.path.isdir(target_path) or os.path.exists(target_path): - try: - if os.path.islink(target_path): - os.unlink(target_path) - else: - shutil.rmtree(target_path) - return True - except Exception as error: - self.output.set_error("Failed to delete the directory: {}". - format(target_path)) - self.output.set_error("Reason: {}". - format(str(error))) - return False - else: - self.output.set_error("Failed to delete the directory: {}". - format(target_path)) - self.output.set_error( - "Target file is not directory or not exists.". - format(target_path)) - return False - - def _clear_directory(self, target_path): - print("append = 'clear'") - if os.path.isdir(target_path): - if os.path.exists(target_path): + if os.path.exists(target_path): + if os.path.isdir(target_path): try: if os.path.islink(target_path): os.unlink(target_path) else: shutil.rmtree(target_path) - return except Exception as error: raise TemplateExecutorError( - ("Failed to delete the directory: {}," - "reason: {}").format(target_path, - str(error))) + ("Failed to delete the directory: {0}," + " reason: {1}").format(target_path, + str(error))) else: - error_message = "target directory does not exist" + error_message = "target file is not directory" else: - error_message = "target file is not directory" + error_message = "target file does not exist" - raise TemplateExecutorError( - "Failed to delete the directory: {}, reason: {}.". - format(target_path, error_message)) - - def _append_join_file(self, target_path): - print("append = 'join'") - if not self.template_parameters.format: - print('Format not defined.') - return - - format_class = self.formats_classes[self.template_parameters.format] + raise TemplateExecutorError(("Failed to delete the directory: {0}," + "reason: {1}").format(target_path, + error_message)) + def _clear_directory(self, target_path): if os.path.exists(target_path): - with open(target_path, 'r') as original_file: - original_file_text = original_file.read() - print('ORIGINAL:') - print(original_file_text) + if os.path.isdir(target_path): + for node in os.scandir(target_path): + if node.is_dir(): + self._remove_directory(node.path) + else: + self._remove_file(node.path) + else: + error_message = "target file is not directory" else: - open(target_path, 'w').close() - original_file_text = '' - - original_object = format_class(original_file_text) - template_object = format_class(self.template_text) + error_message = "target directory does not exist" - print('TEMPLATE:') - print(self.template_text) + raise TemplateExecutorError(("failed to delete directory: {}," + " reason: {}").format(target_path, + error_message)) - original_object.join_template(template_object) + def _link_directory(self, target_path, source): + try: + os.symlink(source, target_path, target_is_directory=True) + print('linked: {0} -> {1}'.format(os.path.basename(target_path), + os.path.basename(source))) + except OSError: + raise TemplateExecutorError("Failed to create symlink: {0} -> {1}". + format(target_path, self.source)) - print('RESULT:') - print(original_object.document_text) + def _execute_template(self, template_object): + pass - with open(target_path, 'w') as target_file: - target_file.write(original_object.document_text) + def _remove_file(self, target_path): + if os.path.islink(target_path): + try: + os.unlink(target_path) + except OSError: + raise TemplateExecutorError('failed to delete the link: {}'. + format(target_path)) + if os.path.isfile(target_path): + try: + os.remove(target_path) + except OSError: + self.output.set_error('failed to delete the file: {}'. + format(target_path)) - def _append_clear_file(self, target_path): - print("append = 'clear'") + def _clear_file(self, target_path): try: - with open(target_path, 'w') as file: - file.truncate(0) + with open(target_path, 'w') as f: + f.truncate(0) except IOError: - raise TemplateExecutorError("Failed to clear the file: {}". + raise TemplateExecutorError("failed to clear the file: {}". format(target_path)) + + def _link_file(self, target_path): + pass + + def chown_directory(self, target_path, chown_value={}): + """Сменить владельца директории.""" + if not chown_value: + chown_value = self.template_parameters.chown + print('chown value = {}'.format(chown_value)) + try: + os.chown(target_path, chown_value['uid'], chown_value['gid']) + except (OSError, Exception) as error: + # возможно потребуются дополнительные проверки. + self.output.set_error('Can not chown directory: {}'. + format(target_path)) + return False + + def chmod_directory(self, target_path, chmod_value=False): + """Сменить права доступа к директории.""" + if not chmod_value: + chmod_value = self.template_parameters.chmod + print('chmod value = {}'.format(chmod_value)) + try: + os.chmod(target_path, chmod_value) + except (OSError, Exception) as error: + # возможно потребуются дополнительные проверки. + self.output.set_error('Can not chmod directory: {}'. + format(target_path)) + self.output.set_error('reason: {}'.format(str(error))) return False + def chown_file(self, target_path, check_existation=True): + """Сменить владельца файла.""" + try: + if check_existation and not os.path.exists(target_path): + open(target_path, 'w').close() + os.lchown(target_path, self.chown['uid'], self.chown['gid']) + return True + except (OSError, Exception) as error: + # возможно потребуются дополнительные проверки. + self.output.set_error('Can not chown file: {}'.format(target_path)) + return False + + def chmod_file(self, target_path, check_existation=True): + """Сменить права доступа к директории.""" + try: + if check_existation and not os.path.exists(target_path): + open(target_path, 'w').close() + os.chmod(target_path, self.chmod) + return True + except (OSError, Exception) as error: + # возможно потребуются дополнительные проверки. + self.output.set_error('Can not chmod file: {}'.format(target_path)) + return False + + def get_file_info(self, path, info='all'): + file_stat = os.stat(path) + if info == 'all': + return stat.S_IMODE(file_stat.st_mode), file_stat.st_uid,\ + file_stat.st_gid + if info == 'mode': + return stat.S_IMODE(file_stat.st_mode) + if info == 'owner': + return file_stat.st_uid, file_stat.st_gid + + def set_uid_gid_error(self, path, uid, gid, template_path=''): + import pwd + import grp + try: + user_name = pwd.getpwuid(uid).pw_name + except (TypeError, KeyError): + user_name = str(uid) + try: + group_name = grp.getgrgid(gid).gr_name + except (TypeError, KeyError): + group_name = str(gid) + owner = '{0}:{1}'.format(user_name, group_name) + if template_path: + self.output.set_error('Failed to process template file {}'. + template_path) + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # !! описать ошибку !! + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + self.output.set_error('error with owner: {}'.format(owner)) + + def check_filesystem(self, target_path): + pass + + def is_vfat(self, path): + if self.mounts is None: + self.mounts = Mounts() + if self.mounts.get_from_fstab(what=self.mounts.TYPE, + where=self.mounts.DIR, + is_in=path) in ('vfat', 'ntfs-3g', + 'ntfs'): + return True + return False + + def check_os_error(self, error, path): + if hasattr(error, 'errno') and error.errno == os.errno.EPERM: + if self.is_vfat(path): + return True + if hasattr(error, 'errno') and error.errno == os.errno.EACCES and\ + 'var/calculate/remote' in path: + return True + return False + # Применение основного шаблона: template_engine.process_template_from_string(template_text, FILE)