From 2e846c9586c7aed1aa1156d51f17b426faa790e4 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: Fri, 22 May 2020 18:08:09 +0300 Subject: [PATCH] Method for append = 'join' for files is almost implemented. --- calculate/utils/package.py | 49 ++++-- template_action_draft.py | 309 +++++++++++++++++++------------------ 2 files changed, 201 insertions(+), 157 deletions(-) diff --git a/calculate/utils/package.py b/calculate/utils/package.py index 2528bdc..caccad0 100644 --- a/calculate/utils/package.py +++ b/calculate/utils/package.py @@ -32,7 +32,7 @@ class PackageNotFound(Exception): class Version: - '''Временный класс для работы со значениями версий.''' + '''Класс для работы со значениями версий.''' def __init__(self, version_value=None): if version_value is None: self._version_string = '-1' @@ -55,6 +55,8 @@ class Version: self._version_value = value def _get_version_value(self, version): + '''Вспомогательный метод для получения значения версии, представленного + в виде списка.''' if isinstance(version, Version): return version._version_value @@ -88,6 +90,8 @@ class Version: return version_value def _use_compare_operation(self, compare_operator, other_value): + '''Вспомогательный метод для реализации различных операций сравнения. + ''' version_value = self._version_value[:] other_value_length = len(other_value) @@ -193,6 +197,8 @@ class Version: class PackageAtomName: + '''Класс для хранения результата определения пакета. Для определения пакета + использует путь к его pkg директории.''' def __init__(self, atom_dictionary): self._package_directory = atom_dictionary['pkg_path'] self._version = atom_dictionary['version'] @@ -261,6 +267,8 @@ class PackageAtomName: class PackageAtomParser: + '''Класс для парсинга параметра package, его проверки, а также определения + принадлежности файла пакету.''' package_name_pattern =\ r'(?P\D[\w\d]*(\-\D[\w\d]*)*)(?P-\d[^\s:]*)?' @@ -286,6 +294,8 @@ class PackageAtomParser: self._atom_dictionary = {} def parse_package_parameter(self, package_atom): + '''Метод для разбора значения package, после разбора инициирует + проверку полученных значений. Возвращает объект PackageAtomName.''' self.package_atom = package_atom self._atom_dictionary = {} @@ -328,6 +338,8 @@ class PackageAtomParser: return atom_name_object def _check_package_existance(self, package_atom=''): + '''Метод для проверки существования пакета. Существование пакета + определяется наличием соответствующего CONTENTS файла.''' if package_atom: self.parse_package_parameter(package_atom) return True @@ -400,6 +412,8 @@ class PackageAtomParser: self._atom_dictionary['version'] = packages[pkg_path] def _check_slot_value(self, pkg_path): + '''Метод для проверки полученного из параметра package значения slot. + ''' if 'slot' in self._atom_dictionary: slot = self._get_slot_value(pkg_path) @@ -410,6 +424,8 @@ class PackageAtomParser: errno=NOTEXIST) def _check_use_flags_value(self, pkg_path): + '''Метод для проверки полученных из параметра package значений + use-флагов.''' if 'use_flags' in self._atom_dictionary: use_flags = self._get_use_flags_value(pkg_path) @@ -422,6 +438,7 @@ class PackageAtomParser: errno=NOTEXIST) def _get_slot_value(self, pkg_path): + '''Метод для получения значения slot из файла SLOT.''' slot_path = os.path.join(pkg_path, 'SLOT') try: return read_file(slot_path).strip('\n') @@ -430,6 +447,7 @@ class PackageAtomParser: " 'package': {}".format(self.package_atom)) def _get_use_flags_value(self, pkg_path): + '''Метод для получения списка значений use-флагов из файла USE.''' use_path = os.path.join(pkg_path, 'USE') try: return read_file(use_path).strip('\n').split(' ') @@ -438,11 +456,14 @@ class PackageAtomParser: " parameter: {}".format(self.package_atom)) def _get_category_packages(self, category): + '''Генератор имен категорий, имеющихся в /var/db/pkg''' for path in glob.glob('{0}/{1}/*/CONTENTS'.format(self.pkg_path, category)): yield path def get_file_package(self, file_path): + '''Метод для определения пакета, которому принадлежит файл.''' + # Удаляем часть пути соответствующую chroot_path if self.chroot_path != '/' and file_path.startswith(self.chroot_path): file_path = file_path[len(self.chroot_path):] @@ -595,7 +616,7 @@ class Package: contents_item = OrderedDict({'type': 'dir'}) self.contents_dictionary[file_name] = contents_item - def add_sym(self, file_name): + def add_sym(self, file_name, target_path=None): file_name = self.remove_cfg_prefix(file_name) real_path = file_name @@ -604,18 +625,23 @@ class Package: if real_path == file_name: real_path = join_paths(self.chroot_path, file_name) + if target_path is None: + target = read_link(real_path) + else: + target = target_path + self.add_dir(os.path.dirname(file_name)) mtime = str(int(os.lstat(real_path).st_mtime)) try: contents_item = OrderedDict({'type': 'sym', - 'target': read_link(real_path), + 'target': target, 'mtime': mtime}) except FilesError as error: raise PackageError(str(error)) self.contents_dictionary[file_name] = contents_item - def add_obj(self, file_name): + def add_obj(self, file_name, file_md5=None): file_name = self.remove_cfg_prefix(file_name) real_path = file_name @@ -624,14 +650,17 @@ class Package: if real_path == file_name: real_path = join_paths(self.chroot_path, file_name) self.add_dir(os.path.dirname(file_name)) - - try: - file_text = read_file(real_path).encode() - except FilesError as error: - raise PackageError(str(error)) + if file_md5 is not None: + md5_value = file_md5 + else: + try: + file_text = read_file(real_path).encode() + except FilesError as error: + raise PackageError(str(error)) + md5_value = hashlib.md5(file_text).hexdigest() contents_item = OrderedDict({'type': 'obj', - 'md5': hashlib.md5(file_text).hexdigest(), + 'md5': md5_value, 'mtime': str(int(os.lstat(real_path).st_mtime))}) self.contents_dictionary[file_name] = contents_item diff --git a/template_action_draft.py b/template_action_draft.py index 50d2a92..d1ce818 100644 --- a/template_action_draft.py +++ b/template_action_draft.py @@ -97,7 +97,6 @@ class CalculateConfigFile: def set_files_md5(self, file_path, file_md5): file_path = self._remove_chroot(file_path) self._config_dictionary[file_path] = file_md5 - self._unsaved_changes = True def remove_file(self, file_path): @@ -108,7 +107,10 @@ class CalculateConfigFile: def compare_md5(self, file_path, file_md5): file_path = self._remove_chroot(file_path) - return self._config_dictionary[file_path] == file_md5 + if file_path in self._config_dictionary: + return self._config_dictionary[file_path] == file_md5 + else: + return False def save_changes(self): config_file = write_file(self.cl_config_path) @@ -144,7 +146,6 @@ class TemplateWrapper: config_archive_path = '/var/lib/calculate/config-archive' package_atom_parser = PackageAtomParser(chroot_path=chroot_path) - calculate_config_file = CalculateConfigFile() _protected_is_set = False _protected_set = {'/etc'} @@ -156,6 +157,10 @@ class TemplateWrapper: self.target_package_name = None + # Вспомогательный флаг, включается, если по целевому пути лежит файл, + # для которого не определился никакой пакет. + self.target_without_package = False + self.parameters = parameters self.output_path = self.target_path @@ -168,18 +173,8 @@ class TemplateWrapper: # применением шаблона. self.remove_original = False - # Флаг, разрешающий изменение хэш-суммы в contents после отработки - # шаблона. - 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 + # Временный флаг для определения того, является ли шаблон userspace. + self.is_userspace = False # Получаем класс соответствующего формата файла. if self.parameters.format: @@ -271,109 +266,90 @@ class TemplateWrapper: '''Проверка на предмет коллизии, то есть конфликта пакета шаблона и целевого файла.''' if self.parameters.package: - self.target_package_name = self.parameters.package - - if self.target_type is None: - return + parameter_package = self.parameters.package + else: + parameter_package = None - try: - file_package = self.package_atom_parser.get_file_package( + if self.target_type is not None: + try: + file_package = self.package_atom_parser.get_file_package( self.target_path) - except PackageNotFound: - return - - if self.target_package_name is None: + except PackageNotFound: + file_package = None + self.target_without_package = True + else: + file_package = None + + # Если для шаблона и целевого файла никаким образом не удается + # определить пакет и есть параметр append -- шаблон пропускаем. + if parameter_package is None and file_package is None: + if self.parameters.append: + raise TemplateCollisionError( + "'package' parameter is not defined for" + " template with 'append' parameter.") + if parameter_package is None: self.target_package_name = file_package - elif file_package != self.target_package_name: + + if file_package is None: + self.target_package_name = parameter_package + + if file_package != parameter_package: raise TemplateCollisionError( "The template package is {0} while target" " file package is {1}").format( self.target_package_name.atom, file_package.atom ) + else: + self.target_package_name = parameter_package + self.target_package = Package(self.target_package_name, chroot_path=self.chroot_path) def check_user_changes(self): - if (self.template_type is None or + if self.target_type is None: + self.md5_matching = True + elif self.target_without_package: + if self.parameters.autoupdate: + self.md5_matching = True + self.remove_original = True + else: + self.md5_matching = False + + if (self.target_type is None or self.target_package is None or self.target_type != FILE): return + # Собираем список имеющихся ._cfg файлов. + cfg_pattern = os.path.join(os.path.dirname(self.target_path), + "._cfg????_{}".format( + os.path.basename(self.target_path))) + self.cfg_list = glob.glob(cfg_pattern) + target_md5 = self.target_package.get_md5(self.target_path) - md5_comparision_result = self.target_package.is_md5_equal( + self.md5_matching = self.target_package.is_md5_equal( self.target_path, file_md5=target_md5) + self.md5_matching = self.md5_matching or self.parameters.autoupdate + self.archive_path = self._get_archive_path(self.target_path) - 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 - - # Чем обновляем CONTENTS - self.update_contents = self.target_path - - # Обновляем CA - # Должна быть какая-то проверка на предмет userspace. - self.update_archive = self._get_archive_path(self.target_path) - - # Очищаем CL - self.clean_config = True + if self.md5_matching: + # Приоритет отдаем пути из параметра source. + if self.parameters.source: + self.input_path = self.parameters.source else: - # Приоритет отдаем пути из параметра source. - if self.parameters.source: - self.input_path = self.parameters.source - else: - self.input_path = self.target_path - self.output_path = self.target_path - - # Чем обновляем CONTENTS - self.update_contents = self.target_path + self.input_path = 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 + self.output_path = self.target_path else: - 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 + # Приоритет отдаем пути из параметра source. + if self.parameters.source: + self.input_path = self.parameters.source else: - # Приоритет отдаем пути из параметра 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.input_path = self.archive_path - self.check_config = True + self.output_path = self._get_cfg_path(self.target_path) def _get_archive_path(self, file_path): if self.chroot_path != "/" and file_path.startswith(self.chroot_path): @@ -399,32 +375,16 @@ class TemplateWrapper: return new_cfg_path - def _update_contents(self): + def remove_from_contents(self): pass - def use_format(self): - if self.format_class.EXECUTABLE: - # Для исполняемых форматов. - return - - if self.target_type is None: - original_file_text = '' - else: - original_file = open(target_path, 'r') - original_file_text = original_file.read() - original_file.close() - - original_file = open(target_path, 'w') - - original_object = self.format_class(original_file_text) - template_object = self.format_class(self.template_text) - original_object.join_template(template_object) - - original_file.write(original_object.document_text) - original_file.close() - - def accept_changes(self): - pass + def add_to_contents(self, file_md5=None): + if self.parameters.append == 'link': + self.target_package.add_sym(target_path, self.parameters.source) + elif self.template_type == DIR: + self.target_package.add_dir(target_path) + elif self.template_type == FILE: + self.target_package.add_obj(target_path, file_md5) @classmethod def _set_protected(cls): @@ -464,12 +424,12 @@ class TemplateExecutor: self.file_appends = {'join': self._append_join_file} self.formats_classes = ParametersProcessor.available_formats + self.calculate_config_file = CalculateConfigFile( + cl_config_path=cl_config_path, + cl_chroot_path=chroot_path) TemplateWrapper.chroot_path = self.chroot_path TemplateWrapper.cl_config_archive = cl_config_archive - TemplateWrapper.calculate_config_file = CalculateConfigFile( - cl_config_path=cl_config_path, - cl_chroot_path=chroot_path) @property def available_appends(self): @@ -483,64 +443,119 @@ class TemplateExecutor: 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: + print('type conflict: {}'.format(str(error))) + except TemplateCollisionError as error: + print('collision: {}'.format(str(error))) 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) + template_object.target_type = None # Добавить поддержку run, excute и т.д. - self.directory_appends[template_object.parameters.append]( + if template_object.template_type == DIR: + self.directory_appends[template_object.parameters.append]( + template_object) + elif template_object.template_type == FILE: + self.file_appends[template_object.parameters.append]( template_object) def _append_join_directory(self, template_object: TemplateWrapper): - pass + if template_object.target_type is None: + self._create_directory(template_object) + template_object.add_to_contents() + else: + pass def _append_remove_directory(self, template_object: TemplateWrapper): - pass + if template_object.target_type is not None: + self._remove_directory(template_object.target_path) + template_object.remove_from_contents() + else: + pass def _append_clear_directory(self, template_object: TemplateWrapper): - pass + if template_object.target_type is not None: + # Подумать об организации очистки CONTENTS для этого append. + pass def _append_join_file(self, template_object: TemplateWrapper): + '''Метод описывающий действия при append = "join".''' 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() + if template_object.md5_matching: + output_paths = [output_path] + # Проверка на предмет userspace. + if template_object.is_userspace: + output_paths.append(template_object.archive_path) + + if template_object.target_type is not None: + with open(input_path, 'r') as input_file: + input_text = input_file.read() + else: + input_text = '' parsed_input = template_format(input_text) - parsed_template = template_format(template_object.template_text) - parsed_input.join_template(parsed_template) + 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() + # Результат наложения шаблона. + output_text = parsed_input.document_text + # Удаляем форматный объект входного файла. + del(parsed_input) + output_text_md5 = hashlib.md5(output_text.encode()).hexdigest() - with open(output_path, 'w') as output_file: - output_file.write(parsed_input.document_text) + for save_path in output_paths: + with open(save_path, 'w') as output_file: + output_file.write(output_text) - if template_object.clean_cfg: - for cfg_file_path in template_object.cfg_list: - self._remove_file(cfg_file_path) + # Убираем все ._cfg файлы. + if template_object.cfg_list: + 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) + # Убираем целевой файл из CL. + self.calculate_config_file.remove_file(template_object.target_path) - parsed_ca.join_template(parsed_template) - ca_text = parsed_ca.document_text + # Обновляем CONTENTS. + template_object.add_to_contents(file_md5=output_text_md5) + else: + 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 + # Удаляем форматный объект входного файла. + del(parsed_input) + output_text_md5 = hashlib.md5(output_text.encode()).hexdigest() + + if not self.calculate_config_file.compare_md5(target_path, + output_text_md5): + with open(output_path, 'w') as output_file: + output_file.write(output_text) + self.calculate_config_file.set_files_md5(template_object, + output_text_md5) + else: + # Действия если CL совпало. Пока ничего не делаем. + pass - template_object._update_contents() + # Обновляем CONTENTS. + template_object.add_to_contents(file_md5=output_text_md5) def _create_directory(self, template_object: TemplateWrapper): target_path = template_object.target_path