diff --git a/calculate/templates/format/base_format.py b/calculate/templates/format/base_format.py index 473aab1..442aa22 100644 --- a/calculate/templates/format/base_format.py +++ b/calculate/templates/format/base_format.py @@ -15,11 +15,12 @@ class FormatError(Exception): class BaseFormat(): + FORMAT = 'none' + def __init__(self, processing_methods): self._processing_methods = processing_methods self._document_dictionary = OrderedDict() self._item_to_add = OrderedDict() - self._format = 'none' self.TEMPLATES_DIRECTORY = 'templates' @@ -295,7 +296,7 @@ class BaseFormat(): lstrip_blocks=True) formats_environment.globals.update(zip=zip) formats_environment.add_extension('jinja2.ext.do') - template = formats_environment.get_template(self._format) + template = formats_environment.get_template(self.FORMAT) document_text = template.render( document_dictionary=self._document_dictionary ) diff --git a/calculate/templates/format/bind_format.py b/calculate/templates/format/bind_format.py index 76b9b66..6dc8255 100644 --- a/calculate/templates/format/bind_format.py +++ b/calculate/templates/format/bind_format.py @@ -11,6 +11,8 @@ from pyparsing import originalTextFor, OneOrMore, Word, alphanums, Literal,\ class BINDFormat(BaseFormat): + FORMAT = 'bind' + def __init__(self, document_text: str, ignore_comments=False, join_before=False, @@ -23,7 +25,6 @@ class BINDFormat(BaseFormat): self._comments_processing = True self._join_before = join_before self._comment_symbol = comment_symbol - self._format = 'bind' self._last_comments_list = [] self._initialize_parser() diff --git a/calculate/templates/format/compiz_format.py b/calculate/templates/format/compiz_format.py index 43693e8..97a9bc9 100644 --- a/calculate/templates/format/compiz_format.py +++ b/calculate/templates/format/compiz_format.py @@ -8,6 +8,8 @@ from pyparsing import originalTextFor, Literal, ZeroOrMore, Word, printables,\ class CompizFormat(BaseFormat): + FORMAT = 'compiz' + _initialized = False _comment_symbol = '' @@ -23,7 +25,6 @@ class CompizFormat(BaseFormat): self._comments_processing = True self._join_before = join_before self._need_finish = True - self._format = 'compiz' self._current_section = OrderedDict() self._current_section_name = '' diff --git a/calculate/templates/format/contents_format.py b/calculate/templates/format/contents_format.py new file mode 100644 index 0000000..2bc9976 --- /dev/null +++ b/calculate/templates/format/contents_format.py @@ -0,0 +1,140 @@ +# vim: fileencoding=utf-8 +# +from .base_format import BaseFormat +from collections import OrderedDict +from jinja2 import PackageLoader, Environment +from pyparsing import Literal, Regex, Word, nums, alphanums, Optional,\ + ParseException + + +class ContentsFormat(BaseFormat): + FORMAT = 'contents' + + _initialized = False + + def __init__(self, document_text: str, + ignore_comments=False, + join_before=False, + template_parser=True): + processing_methods = [self._parse_dir_line, + self._parse_sym_line, + self._parse_obj_line] + + super().__init__(processing_methods) + + self._ignore_comments = ignore_comments + self._join_before = join_before + self._template_parser_flag = template_parser + + if not self._initialized: + self._initialize_parser() + + if document_text == '': + self._document_dictionary = OrderedDict() + else: + document_lines = self._get_list_of_logic_lines(document_text) + self._lines_to_dictionary(document_lines) + + @classmethod + def _initialize_parser(cls): + action_symbols = (Literal('!') | Literal('-')) + + sym_keyword = Literal('sym') + dir_keyword = Literal('dir') + obj_keyword = Literal('obj') + + symlink_arrow = Literal('->') + + file_path = Regex(r'\S+') + + time_value = Word(nums) + + md5 = Word(alphanums) + + cls.sym_line = (Optional(action_symbols, default='')('action') + + sym_keyword('type') + file_path('name') + + symlink_arrow.suppress() + file_path('target') + + time_value('time')) + + cls.dir_line = (Optional(action_symbols, default='')('action') + + dir_keyword('type') + file_path('name')) + + cls.obj_line = (Optional(action_symbols, default='')('action') + + obj_keyword('type') + file_path('name') + + md5('md5') + time_value('time')) + + cls._initialized = True + + def _parse_sym_line(self, line): + try: + parsing_result = self.sym_line.parseString(line) + self._match = True + if self._template_parser_flag: + output_name = (parsing_result.action, parsing_result.name) + output_value = (parsing_result.type, + parsing_result.target, + parsing_result.time) + self._item_to_add = OrderedDict({output_name: [output_value]}) + else: + output_name = parsing_result.name + output_value = OrderedDict({'type': parsing_result.type, + 'target': parsing_result.target, + 'mtime': parsing_result.time}) + + self._item_to_add = OrderedDict({output_name: output_value}) + self._ready_to_update = True + except ParseException: + return + + def _parse_dir_line(self, line): + try: + parsing_result = self.dir_line.parseString(line) + self._match = True + if self._template_parser_flag: + output_name = (parsing_result.action, parsing_result.name) + output_value = (parsing_result.type,) + self._item_to_add = OrderedDict({output_name: [output_value]}) + else: + output_name = parsing_result.name + output_value = OrderedDict({'type': parsing_result.type}) + + self._item_to_add = OrderedDict({output_name: output_value}) + self._ready_to_update = True + except ParseException: + return + + def _parse_obj_line(self, line): + try: + parsing_result = self.obj_line.parseString(line) + self._match = True + if self._template_parser_flag: + output_name = (parsing_result.action, parsing_result.name) + output_value = (parsing_result.type, + parsing_result.md5, + parsing_result.time) + self._item_to_add = OrderedDict({output_name: [output_value]}) + else: + output_name = parsing_result.name + output_value = OrderedDict({'type': parsing_result.type, + 'md5': parsing_result.md5, + 'mtime': parsing_result.time}) + self._item_to_add = OrderedDict({output_name: output_value}) + + self._ready_to_update = True + except ParseException: + return + + def get_document_text(self): + file_loader = PackageLoader('calculate.templates.format', + self.TEMPLATES_DIRECTORY) + formats_environment = Environment(loader=file_loader, + trim_blocks=True, + lstrip_blocks=True) + formats_environment.globals.update(zip=zip) + formats_environment.add_extension('jinja2.ext.do') + template = formats_environment.get_template(self.FORMAT) + document_text = template.render( + document_dictionary=self._document_dictionary, + template_parser=self._template_parser_flag + ) + return document_text diff --git a/calculate/templates/format/diff_format.py b/calculate/templates/format/diff_format.py index f04627e..9ef178b 100644 --- a/calculate/templates/format/diff_format.py +++ b/calculate/templates/format/diff_format.py @@ -1,11 +1,14 @@ # vim: fileencoding=utf-8 # +from .base_format import BaseFormat from calculate.utils.files import Process from calculate.templates.format.base_format import FormatError from os import path -class DiffFormat(): +class DiffFormat(BaseFormat): + FORMAT = 'diff' + def __init__(self, document_text: str, comment_symbol=''): self._patch_text = document_text self._root_path = '' diff --git a/calculate/templates/format/dovecot_format.py b/calculate/templates/format/dovecot_format.py index 5294bf5..94e8f36 100644 --- a/calculate/templates/format/dovecot_format.py +++ b/calculate/templates/format/dovecot_format.py @@ -11,6 +11,8 @@ from pyparsing import originalTextFor, Literal, ZeroOrMore, Word, printables,\ class DovecotFormat(BaseFormat): + FORMAT = 'dovecot' + _initialized = False _comment_symbol = '' @@ -28,7 +30,6 @@ class DovecotFormat(BaseFormat): self._comments_processing = True self._need_finish = True self._join_before = join_before - self._format = 'dovecot' self._section_stack = OrderedDict() self._current_section_name = '' diff --git a/calculate/templates/format/json_format.py b/calculate/templates/format/json_format.py index 27bc30b..cf93fe4 100644 --- a/calculate/templates/format/json_format.py +++ b/calculate/templates/format/json_format.py @@ -6,6 +6,8 @@ import json class JSONFormat(BaseFormat): + FORMAT = 'json' + def __init__(self, document_text: str, ignore_comments=False, join_before=False, comment_symbol=''): processing_methods = [] @@ -13,7 +15,6 @@ class JSONFormat(BaseFormat): self._ignore_comments = ignore_comments self._join_before = join_before self._comments_processing = False - self._format = 'json' if document_text == '': self._document_dictionary = OrderedDict() diff --git a/calculate/templates/format/kde_format.py b/calculate/templates/format/kde_format.py index f55a574..9771fb4 100644 --- a/calculate/templates/format/kde_format.py +++ b/calculate/templates/format/kde_format.py @@ -8,6 +8,8 @@ from pyparsing import originalTextFor, Literal, ZeroOrMore, Word, printables,\ class KDEFormat(BaseFormat): + FORMAT = 'kde' + _initialized = False def __init__(self, document_text: str, ignore_comments=False, @@ -22,7 +24,6 @@ class KDEFormat(BaseFormat): self._comments_processing = True self._join_before = join_before self._need_finish = True - self._format = 'kde' self._current_section = OrderedDict() self._current_section_name = '' diff --git a/calculate/templates/format/kernel_format.py b/calculate/templates/format/kernel_format.py index 02f0179..fbf1f67 100644 --- a/calculate/templates/format/kernel_format.py +++ b/calculate/templates/format/kernel_format.py @@ -8,6 +8,8 @@ from pyparsing import Word, Literal, alphanums, printables, originalTextFor,\ class KernelFormat(BaseFormat): + FORMAT = 'kernel' + _initialized = False def __init__(self, document_text: str, ignore_comments=False, @@ -20,7 +22,6 @@ class KernelFormat(BaseFormat): self._ignore_comments = ignore_comments self._join_before = join_before self._comments_processing = True - self._format = 'kernel' self._last_comments_list = [] if not self._initialized: diff --git a/calculate/templates/format/ldap_format.py b/calculate/templates/format/ldap_format.py index 61d40f2..2e2f497 100644 --- a/calculate/templates/format/ldap_format.py +++ b/calculate/templates/format/ldap_format.py @@ -9,6 +9,8 @@ from pyparsing import originalTextFor, Literal, ZeroOrMore, Word, printables,\ class LDAPFormat(BaseFormat): + FORMAT = 'ldap' + _initialized = False def __init__(self, document_text: str, @@ -32,7 +34,6 @@ class LDAPFormat(BaseFormat): self._comments_processing = True self._join_before = join_before self._need_finish = True - self._format = 'ldap' if self._ignore_comments: self._current_type_section = OrderedDict() diff --git a/calculate/templates/format/openrc_format.py b/calculate/templates/format/openrc_format.py index 8777051..55adb5f 100644 --- a/calculate/templates/format/openrc_format.py +++ b/calculate/templates/format/openrc_format.py @@ -8,6 +8,8 @@ from pyparsing import Word, Literal, printables, originalTextFor, ZeroOrMore,\ class OpenRCFormat(BaseFormat): + FORMAT = 'openrc' + _initialized = False def __init__(self, document_text: str, @@ -21,7 +23,6 @@ class OpenRCFormat(BaseFormat): super().__init__(processing_methods) self._ignore_comments = ignore_comments self._comments_processing = True - self._format = 'openrc' self._last_comments_list = [] if not self._initialized: diff --git a/calculate/templates/format/patch_format.py b/calculate/templates/format/patch_format.py index cbc7c10..046fd1d 100644 --- a/calculate/templates/format/patch_format.py +++ b/calculate/templates/format/patch_format.py @@ -10,11 +10,13 @@ except ImportError: class PatchFormat(BaseFormat): + FORMAT = 'patch' + FORMAT_PARAMETERS = {'multiline', 'dotall', 'comment'} + def __init__(self, document_text: str, multiline=False, dotall=False, comment_symbol=''): processing_methods = OrderedDict() super().__init__(processing_methods) - self._format = 'patch' self._multiline_flag = multiline self._dotall_flag = dotall diff --git a/calculate/templates/format/postfix_format.py b/calculate/templates/format/postfix_format.py index 073c9ef..51f3fa6 100644 --- a/calculate/templates/format/postfix_format.py +++ b/calculate/templates/format/postfix_format.py @@ -8,6 +8,8 @@ from pyparsing import Word, Literal, alphanums, printables, originalTextFor,\ class PostfixFormat(BaseFormat): + FORMAT = 'postfix' + _initialized = False def __init__(self, document_text: str, @@ -21,7 +23,6 @@ class PostfixFormat(BaseFormat): super().__init__(processing_methods) self._ignore_comments = ignore_comments self._comments_processing = True - self._format = 'postfix' self._last_comments_list = [] if not self._initialized: diff --git a/calculate/templates/format/procmail_format.py b/calculate/templates/format/procmail_format.py index f7cc807..2ffb5a3 100644 --- a/calculate/templates/format/procmail_format.py +++ b/calculate/templates/format/procmail_format.py @@ -8,6 +8,8 @@ from pyparsing import Word, Literal, alphanums, printables, originalTextFor,\ class ProcmailFormat(BaseFormat): + FORMAT = 'procmail' + _initialized = False def __init__(self, document_text: str, @@ -21,7 +23,6 @@ class ProcmailFormat(BaseFormat): super().__init__(processing_methods) self._ignore_comments = ignore_comments self._comments_processing = True - self._format = 'procmail' self._last_comments_list = [] if not self._initialized: diff --git a/calculate/templates/format/proftpd_format.py b/calculate/templates/format/proftpd_format.py index 07a934a..c8f999b 100644 --- a/calculate/templates/format/proftpd_format.py +++ b/calculate/templates/format/proftpd_format.py @@ -9,6 +9,8 @@ from pyparsing import originalTextFor, Literal, ZeroOrMore, Word, printables,\ class ProFTPDFormat(BaseFormat): + FORMAT = 'proftpd' + _initialized = False def __init__(self, document_text: str, @@ -29,7 +31,6 @@ class ProFTPDFormat(BaseFormat): self._ignore_comments = ignore_comments self._need_finish = True self._comments_processing = True - self._format = 'proftpd' self._section_stack = [] self._actions_stack = [] @@ -412,7 +413,7 @@ class ProFTPDFormat(BaseFormat): formats_environment = Environment(loader=file_loader) formats_environment.globals.update(zip=zip) formats_environment.add_extension('jinja2.ext.do') - template = formats_environment.get_template(self._format) + template = formats_environment.get_template(self.FORMAT) document_text = template.render( document_dictionary=self._document_dictionary ) diff --git a/calculate/templates/format/samba_format.py b/calculate/templates/format/samba_format.py index 5974491..13820af 100644 --- a/calculate/templates/format/samba_format.py +++ b/calculate/templates/format/samba_format.py @@ -7,6 +7,7 @@ from pyparsing import originalTextFor, Literal, Word, printables, OneOrMore,\ class SambaFormat(BaseFormat): + FORMAT = 'samba' _initialized = False def __init__(self, document_text: str, @@ -28,8 +29,6 @@ class SambaFormat(BaseFormat): self._comments_processing = True self._join_before = join_before - self._format = 'samba' - self._last_comments_list = [] if not self._initialized: self._initialize_parser() diff --git a/calculate/templates/format/templates/contents b/calculate/templates/format/templates/contents new file mode 100644 index 0000000..2024fee --- /dev/null +++ b/calculate/templates/format/templates/contents @@ -0,0 +1,14 @@ +{% for file_name, contents_values in document_dictionary.items() %} +{% if contents_values is mapping %} +{% set contents_values = (contents_values.values()|list) %} +{% else %} +{% set contents_values = (contents_values[0]|list) %} +{% set file_name = file_name[-1] %} +{% endif %} +{% if contents_values[0] == 'sym' %} +{{ contents_values[0] }} {{ file_name }} -> {{ contents_values[1:]|join(' ') }} +{% else %} +{{ contents_values[0] }} {{ file_name }}{% if contents_values[1:] %} {{ contents_values[1:]|join(' ') }}{% endif %} + +{% endif %} +{% endfor %} diff --git a/calculate/templates/format/xml_gconf_format.py b/calculate/templates/format/xml_gconf_format.py index 2a1d864..73e8967 100644 --- a/calculate/templates/format/xml_gconf_format.py +++ b/calculate/templates/format/xml_gconf_format.py @@ -12,6 +12,8 @@ except ImportError: class XMLGConfFormat(BaseFormat): + FORMAT = 'xml_gconf' + def __init__(self, document_text: str): processing_methods = OrderedDict({'gconf': self._gconf, 'entry': self._entry, @@ -23,7 +25,6 @@ class XMLGConfFormat(BaseFormat): 'longdesc': self._longdesc, 'unknown': self._unknown}) super().__init__(processing_methods) - self._format = 'xml_gconf' self._initialize_parser() diff --git a/calculate/templates/format/xml_xfce_format.py b/calculate/templates/format/xml_xfce_format.py index a4bb679..627da49 100644 --- a/calculate/templates/format/xml_xfce_format.py +++ b/calculate/templates/format/xml_xfce_format.py @@ -12,14 +12,14 @@ except ImportError: class XMLXfceFormat(BaseFormat): + FORMAT = 'xml_xfce' + def __init__(self, document_text: str, ignore_comments=False): processing_methods = OrderedDict({'channel': self._channel, 'property': self._property, 'value': self._value, 'unknown': self._unknown}) super().__init__(processing_methods) - self._format = 'xml_xfce' - self._initialize_parser() if document_text == '': diff --git a/calculate/templates/template_engine.py b/calculate/templates/template_engine.py index b96370f..d2555a4 100644 --- a/calculate/templates/template_engine.py +++ b/calculate/templates/template_engine.py @@ -87,7 +87,7 @@ class ConditionFailed(TemplateSyntaxError): pass -class Parameters(MutableMapping): +class ParametersContainer(MutableMapping): '''Класс для хранения параметров, взятых из шаблона, и передачи их шаблонизатору.''' def __init__(self, parameters_dictionary={}): @@ -113,7 +113,7 @@ class Parameters(MutableMapping): return len(self.__parameters) def __repr__(self): - return ''.format(self.__parameters) + return ''.format(self.__parameters) @property def parameters(self): @@ -304,7 +304,7 @@ class TemplateEngine: CalculateExtension._datavars = datavars_module self._datavars_module = datavars_module - self._parameters_object = Parameters() + self._parameters_object = ParametersContainer() self._template_text = '' self.environment = Environment(loader=FileSystemLoader(directory_path), @@ -320,7 +320,7 @@ class TemplateEngine: пути.''' CalculateContext._env_set = env template = self.environment.get_template(template_path) - self._parameters_object = Parameters(parameters_dictionary={}) + self._parameters_object = ParametersContainer(parameters_dictionary={}) self._template_text = template.render( __datavars__=self._datavars_module, __parameters__=self._parameters_object @@ -330,7 +330,7 @@ class TemplateEngine: '''Метод для обработки текста шаблона.''' CalculateContext._env_set = env template = self.environment.from_string(string) - self._parameters_object = Parameters(parameters_dictionary={}) + self._parameters_object = ParametersContainer(parameters_dictionary={}) self._template_text = template.render( __datavars__=self._datavars_module, __parameters__=self._parameters_object diff --git a/calculate/templates/template_processor.py b/calculate/templates/template_processor.py index ad6af22..5636fe9 100644 --- a/calculate/templates/template_processor.py +++ b/calculate/templates/template_processor.py @@ -1,128 +1,525 @@ +# vim: fileencoding=utf-8 +# import os -import sys import re import stat import shutil +from ..utils.files import join_paths +from importlib import import_module from .template_engine import TemplateEngine, Variables, ConditionFailed from ..utils.io_module import IOModule from collections import OrderedDict from ..utils.mount import Mounts +from ..utils.package import PackageAtom, PackageAtomError + + +class IncorrectParameter(Exception): + pass + + +class DefaultParameterError(Exception): + pass + + +class TemplateActionError(Exception): + pass + + +class TemplateError(Exception): + pass + + +# Типы шаблона: директория или файл. +DIR, FILE = range(2) + + +class TemplateParameters: + '''Класс для хранения, проверки и разбора параметров шаблона.''' + available_parameters = {'name', 'path', 'append', 'chmod', 'chown', + 'autoupdate', 'env', 'force', 'source', 'format', + 'protected', 'mirror', 'run', 'exec', 'env', + 'package', 'merge', 'postmerge', 'action', + 'rebuild', 'restart', 'stop', 'start'} + + inheritable_parameters = {'chmod': '', 'chown': '', 'autoupdate': False, + 'env': '', 'package': '', 'action': ''} + + available_appends = set() + + directory_default_values = {'chown': 'root:root', + 'chmod': '755'} + + file_default_values = {'chown': 'root:root', + 'chmod': '644'} + + available_formats = set() + format_is_inspected = False + + chmod_value_regular = re.compile( + r'([r-][w-][x-])([r-][w-][x-])([r-][w-][x-])') + + package_atom_parser = PackageAtom() + + def __init__(self, parameters: dict, template_type, chroot_path='/'): + self.template_type = template_type + self.chroot_path = chroot_path + + self._parameters_dictionary = {} + self._inspect_formats_package() + + self.checkers_list = OrderedDict({ + 'package': self.check_package_parameter, + 'append': self.check_append_parameter, + 'rebuild': self.check_rebuild_parameter, + 'restart': self.check_restart_parameter, + 'stop': self.check_stop_parameter, + 'start': self.check_start_parameter, + 'chown': self.check_chown_parameter, + 'chmod': self.check_chmod_parameter, + 'autoupdate': self.check_autoupdate_parameter, + 'source': self.check_source_parameter, + 'force': self.check_force_parameter, + }) + + try: + if template_type == DIR: + self.check_template_parameters(self.directory_default_values) + elif template_type == FILE: + self.check_template_parameters(self.file_default_values) + except IncorrectParameter as error: + raise TemplateError('Default values error: {}'.format(str(error))) + + self.check_template_parameters(parameters) + + def __getattr__(self, parameter_name): + if parameter_name not in self.available_parameters: + raise IncorrectParameter("Unknown parameter: '{}'". + format(parameter_name)) + elif parameter_name not in self._parameters_dictionary: + return False + else: + return self._parameters_dictionary[parameter_name] + + def check_template_parameters(self, parameters): + for parameter_name in parameters: + # Если параметр наследуем и уже встречался до этого -- + # второй раз не проверяем его. + if (parameter_name in self.inheritable_parameters and + not isinstance(self.inheritable_parameters[parameter_name], + bool) and + parameters[parameter_name] == + self.inheritable_parameters[parameter_name]): + continue + + if parameter_name not in self.available_parameters: + raise IncorrectParameter("Unknown parameter '{0}'". + format(parameter_name)) + elif parameter_name in self.checkers_list: + parameter_value = self.checkers_list[parameter_name]( + parameters[parameter_name] + ) + self._parameters_dictionary[parameter_name] = parameter_value + if parameter_name in self.inheritable_parameters: + self.inheritable_parameters[parameter_name] =\ + parameter_value + + def check_package_parameter(self, parameter_value): + try: + self.package_atom_parser.parse_package_parameter(parameter_value) + except PackageAtomError as error: + raise IncorrectParameter(str(error)) + parameter_value = self.package_atom_parser.atom_dictionary + return parameter_value + + def check_append_parameter(self, parameter_value): + if parameter_value not in self.available_appends: + raise IncorrectParameter("Unacceptable value '{}' of parameter" + " 'append'".format(parameter_value)) + return parameter_value + + def check_rebuild_parameter(self, parameter_value): + if isinstance(parameter_value, bool): + raise IncorrectParameter("'rebuild' parameter value is not bool") + elif 'package' not in self._parameters_dictionary: + raise IncorrectParameter(("'source' parameter is set without " + "'package' parameter")) + return parameter_value + + def check_restart_parameter(self, parameter_value): + if parameter_value and isinstance(parameter_value, str): + return parameter_value + else: + raise IncorrectParameter( + "'restart' parameter value is not correct") + + def check_stop_parameter(self, parameter_value): + if parameter_value and isinstance(parameter_value, str): + return parameter_value + else: + raise IncorrectParameter("'stop' parameter value is not correct") + + def check_start_parameter(self, parameter_value): + if parameter_value and isinstance(parameter_value, str): + return parameter_value + else: + raise IncorrectParameter("'start' parameter value is not correct") + + def check_run_parameter(self, parameter_value): + if parameter_value and isinstance(parameter_value, str): + return parameter_value + else: + raise IncorrectParameter("'run' parameter value is nkt correct") + + def check_exec_parameter(self, parameter_value): + if parameter_value and isinstance(parameter_value, str): + return parameter_value + else: + raise IncorrectParameter("'exec' parameter value is not correct") + + def check_chown_parameter(self, parameter_value): + if not parameter_value or isinstance(parameter_value, bool): + raise IncorrectParameter("'chown' parameter value is empty.") + parameter_value = self.get_chown_values(parameter_value) + return parameter_value + + def check_chmod_parameter(self, parameter_value): + result = self.chmod_value_regular.search(parameter_value) + if result: + parameter_value = '' + for group_number in range(3): + current_group = result.groups()[group_number] + num = '' + for sym_number in range(3): + if current_group[sym_number] != '-': + num = num + '1' + else: + num = num + '0' + parameter_value = parameter_value + num + return int(parameter_value, 2) + elif parameter_value.isdigit(): + parameter_value = int(parameter_value, 8) + return parameter_value + else: + raise IncorrectParameter("'chmod' parameter value is not correct") + + def check_source_parameter(self, parameter_value): + if self.chroot_path != '/': + real_path = join_paths(self.chroot_path, parameter_value) + else: + real_path = parameter_value + + if not parameter_value or isinstance(parameter_value, bool): + raise IncorrectParameter("'source' parameter value is empty") + elif (self.template_type == DIR and + ('append' not in self._parameters_dictionary or + self._parameters_dictionary['append'] != 'link')): + raise IncorrectParameter( + ("'source' parameter is set without " + "'append = link' for directory template") + ) + elif not os.path.exists(real_path): + raise IncorrectParameter( + "File from 'source' parameter does not exist") + return os.path.normpath(real_path) + + def check_force_parameter(self, parameter_value): + if isinstance(parameter_value, bool): + return parameter_value + else: + raise IncorrectParameter("'force' parameter value is not bool") + + def check_autoupdate_parameter(self, parameter_value): + print('autoupdate value = {}'.format(parameter_value)) + if isinstance(parameter_value, bool): + return parameter_value + else: + raise IncorrectParameter( + "'autoupdate' parameter value is not bool") + + def get_chown_values(self, chown: str): + """Получить значения uid и gid из параметра chown.""" + if chown and ':' in chown: + user_name, group_name = chown.split(':') + + if user_name.isdigit(): + uid = int(user_name) + else: + import pwd + try: + if self.chroot_path == '/': + uid = pwd.getpwnam(user_name).pw_uid + else: + uid = self.get_uid_from_passwd(user_name) + except (KeyError, TypeError): + self.output.set_error( + format(user_name)) + raise IncorrectParameter( + ("'chown' value '{0}' is not correct:" + "no such user in the system: {1}"). + format(chown, user_name)) + if group_name.isdigit(): + gid = int(group_name) + else: + import grp + try: + if self.chroot_path == '/': + gid = grp.getgrnam(group_name).gr_gid + else: + gid = self.get_gid_from_group(group_name) + except (KeyError, TypeError): + raise IncorrectParameter( + ("'chown' value '{0}' is not correct:" + "no such group in the system: {1}"). + format(chown, group_name)) + return {'uid': uid, 'gid': gid} + else: + raise IncorrectParameter("'chown' value '{0}' is not correct". + format(chown, self.template_path)) + + def get_uid_from_passwd(self, user_name: str): + """Взять uid из chroot passwd файла.""" + passwd_file_path = os.path.join(self.chroot_path, 'etc/passwd') + passwd_dictionary = [] + if os.path.exists(passwd_file_path): + with open(passwd_file_path, 'r') as passwd_file: + for line in passwd_file: + if line.startswith('#'): + continue + passwd_item = tuple(line.split(':')[0:3:2]) + if (len(passwd_item) > 1 and passwd_item[0] + and passwd_item[0]): + passwd_dictionary.append(passwd_item) + passwd_dictionary = dict(passwd_dictionary) + return int(passwd_dictionary[user_name]) + else: + IncorrectParameter("passwd file was not found in {}". + format(passwd_file_path)) + + def get_gid_from_group(self, group_name: str): + """Взять gid из chroot group файла.""" + group_file_path = os.path.join(self.chroot_path, 'etc/group') + group_dictionary = [] + if os.path.exists(group_file_path): + with open(group_file_path, 'r') as group_file: + for line in group_file: + if line.startswith('#'): + continue + group_item = tuple(line.split(':')[0:3:2]) + if len(group_item) > 1 and group_item[0] and group_item[1]: + group_dictionary.append(group_item) + group_dictionary = dict(group_dictionary) + if group_name in group_dictionary: + return int(group_dictionary[group_name]) + else: + IncorrectParameter("'{0}' gid was not found in {1}". + format(group_name, group_file_path)) + else: + IncorrectParameter("group file was not found in {}". + format(group_file_path)) + + @classmethod + def _inspect_formats_package(cls): + '''Метод для определения множества доступных форматов и + предоставляемых ими параметров.''' + if cls.format_is_inspected: + return + parameters_set = set() + format_set = set() + format_directory_path = os.path.join(os.path.dirname(__file__), + 'format') + + for module_name in os.listdir(format_directory_path): + if (os.path.isdir(os.path.join('format', module_name)) or + module_name == '__init__.py'): + continue + + if module_name.endswith('.py'): + module_name = module_name[:-3] + + try: + module = import_module('calculate.templates.format.{}'. + format(module_name)) + for obj in dir(module): + if obj.endswith('Format') and obj != 'BaseFormat': + format_class = getattr(module, obj, False) + + if format_class: + format_set.add(format_class.FORMAT) + parameters = getattr(format_class, + 'FORMAT_PARAMETERS', set()) + parameters_set.update(parameters) + except Exception: + continue + + cls.available_formats = format_set + cls.available_parameters.update(parameters_set) + cls.formats_inspected = True class TemplateAction: - def __init__(self, output_module=IOModule(), base_directory='/'): + def __init__(self, output_module=IOModule(), + chroot_path='/', mounts=None): self.output = output_module - self.mounts = None - self.base_directory = base_directory + self.mounts = mounts + self.chroot_path = chroot_path self.exec_list = OrderedDict() self.DIRECTORY_APPENDS = { - 'remove': self.remove_directory, - 'clear': self.clear_directory, - 'replace': self.remove_directory, - 'link': self.link_directory, - 'join': self.join_directory - } + 'remove': self._append_remove_directory, + 'clear': self._append_clear_directory, + 'replace': self._append_replace_directory, + 'link': self._append_link_directory, + 'join': self._append_join_directory, + 'skip': self._append_skip_directory + } self.FILE_APPENDS = { - 'replace': self.replace_file, - 'remove': self.remove_file, - 'clear': self.clear_file, - 'link': self.link_file, - 'join': self.join_file, - 'before': self.before_file, - 'after': self.after_file - } + 'replace': self._append_replace_file, + 'remove': self._append_remove_file, + 'clear': self._append_clear_file, + 'link': self._append_link_file, + 'join': self._append_join_file, + 'before': self._append_before_file, + 'after': self._append_after_file, + 'skip': self._append_skip_file + } + + appends_set = set(self.DIRECTORY_APPENDS.keys()).union( + set(self.FILE_APPENDS.keys())) + + TemplateParameters.available_appends = appends_set + TemplateParameters._inspect_formats_package() + + self.available_parameters = TemplateParameters.available_parameters + self.available_formats = TemplateParameters.available_formats def process_template_directory(self, target_path, template_path, parameters={}): + '''Метод для выполнения шаблона директории.''' self.template_path = template_path + self.target_path = target_path - # разбираем общие параметры шаблона. - self.force = parameters.pop('force', False) - self.autoupdate = parameters.pop('autoupdate', False) + try: + self.template_parameters = TemplateParameters( + parameters, + template_type=DIR, + chroot_path=self.chroot_path) + except IncorrectParameter as error: + self.output.set_error('Incorrect parameter: {}'.format(str(error))) + return + + self.output.console_output.print_default( + 'With parameters: {}\n'. + format(self.template_parameters._parameters_dictionary) + ) + self.output.set_info('Template path: {}'.format(self.template_path)) + self.output.set_info('Target directory: {}'.format(self.target_path)) + + # Методы соответствующие разным значениям append для директорий. + def _append_join_directory(self): + '''Создать каталог.''' + self._create_directory() + + def _append_remove_directory(self): + '''Удалить каталог и все его содержимое.''' + pass - self.source = parameters.pop('source', False) - if self.source and parameters['append'] != 'link': - self.output.set_error(("'source' parameter is set without " - "'append = link' in template {}"). - format(self.template_path)) - return False + def _append_clear_directory(self): + '''Очистить содержимое каталога, если каталог есть.''' + pass - self.chown = parameters.get('chown', False) - if not self.get_chown_values(self.chown): - return False + def _append_replace_directory(self): + '''Очистить содержимое каталога или создать его.''' + pass - self.chmod = parameters.get('chmod', False) + def _append_link_directory(self): + '''Создать символическую ссылку на указанный каталог.''' + pass + + def _append_skip_directory(self): + pass + + # Непосредственно действия с директориями. + def _create_directory(self, target_path=''): + if not target_path: + target_path = self.target_path - def join_directory(self, target_path, template_path): if os.access(target_path, os.F_OK): + if self.template_parameters.chmod: + self.chmod_directory(target_path) + if self.template_parameters.chown: + self.chown_directory(target_path) return True + directory = target_path - directories_to_create = [] + directories_to_create = [target_path] + directory = os.path.dirname(directory) while not os.access(directory, os.F_OK) and directory: - directory = os.path.split(directory)[0] directories_to_create.append(directory) + directory = os.path.dirname(directory) + try: - current_mode, current_uid, current_gid = self.get_file_info( - target_path, - 'all') + current_mod, current_uid, current_gid = self.get_file_info( + directory, + 'all') + current_chown = {'uid': current_uid, 'gid': current_gid} except OSError: - self.output.set_error("No access to the directory: {}". - format(target_path)) + self.output.set_error('No access to the directory: {}'.format( + directory)) return False - if self.chmode: - mode = self.chmode - else: - mode = current_mode - - if self.chown['uid']: - uid = self.chown['uid'] - else: - uid = current_uid - - if self.chown['gid']: - gid = self.chown['gid'] - else: - gid = current_gid directories_to_create.reverse() - for directory in directories_to_create: + + for directory_path in directories_to_create: try: - if mode: - os.mkdir(directory) - os.chmod(directory, mode) + os.mkdir(directory_path) + + if self.template_parameters.chmod: + self.chmod_directory(directory_path) + else: + self.chmod_directory(directory_path, + chmod_value=current_mod) + + if self.template_parameters.chown: + self.chown_directory else: - os.mkdir(directory) - self.chown_directory(directory) - except OSError: - self.output.set_error('failed to create directory: {}'. - format(directory)) + self.chown_directory(directory_path, + chown_value=current_chown) + + except OSError as error: + self.output.set_error('Failed to create directory: {}'. + format(directory_path)) + self.output.set_error('Reason: {}'.format(str(error))) return False return True - def remove_directory(self, target_path): + def _remove_directory(self, target_path): if os.path.isdir(target_path) or os.path.exists(target_path): try: - shutil.rmtree(target_path) + if os.path.islink(target_path): + os.unlink(target_path) + else: + shutil.rmtree(target_path) return True - except Exception: - self.output.set_error("failed to delete the directory: {}". + 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: {}". + self.output.set_error("Failed to delete the directory: {}". format(target_path)) self.output.set_error( - "target file is not directory or not exists.". + "Target file is not directory or not exists.". format(target_path)) return False - def clear_directory(self, target_path): + def _clear_directory(self, target_path): if os.path.isdir(target_path) and os.path.exists(target_path): for node in os.scandir(target_path): if node.is_dir(): - if self.remove_directory(node.path): + if self._remove_directory(node.path): return True else: self.output.set_error('Failed to delete directory: {}'. @@ -141,79 +538,86 @@ class TemplateAction: 'target file is not directory or does not exist: {}'. format(target_path)) - - def link_directory(self, target_path): + def _link_directory(self, target_path): if not self.source: - self.output.set_error(("'source' parameter is not defined for " - "'append = link' in template {}"). + self.output.set_error("'source' parameter is not defined for " + "'append = link' in template {}". format(self.template_path)) + self.output.set_error('Failed to create symlink: {0} -> {1}'. + format(target_path, self.source)) return False - def replace_directory(self, target_path): - pass + if not os.path.exists(self.source): + self.output.set_error("'source' file does not exist for " + "'append = link' in template {}". + format(self.template_path)) + self.output.set_error('Failed to create symlink: {0} -> {1}'. + format(target_path, self.source)) + return False + + try: + os.symlink(self.source, target_path, target_is_directory=True) + print('linked: {0} -> {1}'.format(os.path.basename(target_path), + os.path.basename(self.source))) + return True + except OSError: + self.output.set_error('Template error: {}'. + format(self.template_path)) + self.output.set_error('Failed to create symlink: {0} -> {1}'. + format(target_path, self.source)) + return False def process_template_file(self, target_path, template_path, template_text='', parameters={}): self.template_text = template_text self.template_path = template_path - - # разбираем общие параметры шаблона. - self.autoupdate = parameters.pop('autoupdate', False) - self.force = parameters.pop('force', False) - self.mirror = parameters.pop('mirror', False) - self.protected = parameters.pop('protected', False) - - self.format = parameters.pop('format', False) - self.source = parameters.pop('source', False) - self.run = parameters.pop('run', False) - self.exec = parameters.pop('exec', False) - self.chmod = parameters.pop('chmod', False) - self.chown = parameters.pop('chown', False) - - if (self.source and parameters['append'] not in - {'link', 'join', 'after', 'before'}): - self.output.set_error(("'source' parameter is set without" - " a suitable 'append' parameter" - " in template {}"). - format(self.template_path)) - return False + self.target_path = target_path try: - append_value = parameters.pop('append', False) - if not append_value: - self.output("'append' parameter is not defined in template {}". - format(self.template_path)) - return False - self.append = self.DIRECTORY_APPENDS[append_value] - except KeyError: - self.output.set_error("Unknown 'append' value in template {}". - format(self.template_path)) - return False + self.template_parameters = TemplateParameters( + parameters, + template_type=FILE, + chroot_path=self.chroot_path) + except IncorrectParameter as error: + self.output.set_error('Incorrect parameter: {}'.format(str(error))) + return + + self.output.console_output.print_default( + 'With parameters: {}\n'. + format(self.template_parameters._parameters_dictionary) + ) + self.output.set_info('Template path: {}'.format(self.template_path)) + self.output.set_info('Target file: {}.\nTemplate text:'. + format(self.target_path)) + self.output.console_output.print_default(self.template_text + '\n\n') + + # Методы соответствующие значениями параметра append для файлов шаблонов. + def _append_replace_file(self): + pass - # получаем uid и gid значения из параметра chown. - if self.chown: - self.chown = self.get_chown_values() - if not self.chown: - return False + def _append_remove_file(self): + pass - # временный параметр format будет заменен на автоопределение формата. - if not self.format: - self.output.set_error( - "'format' parameter is not defined in template {}". - format(self.template_path) - ) - return False + def _append_clear_file(self): + pass + + def _append_link_file(self): + pass + + def _append_join_file(self): + pass - def join_file(self, target_path): + def _append_before_file(self): pass - def before_file(self, target_path): + def _append_after_file(self): pass - def after_file(self, target_path): + def _append_skip_file(self): pass - def replace_file(self, target_path): + # Методы для работы непосредственно с файлами. + def join_file(self, target_path, option=False): pass def remove_file(self, target_path): @@ -254,122 +658,31 @@ class TemplateAction: def link_file(self, target_path): pass - def get_parameter_names(self): - '''Временная замена механизма получения множества - поддерживаемых форматами параметров.''' - parameters_set = {'name', 'path', 'append', 'chmod', 'chown', - 'autoupdate', 'env', 'link', 'force', 'symbolic', - 'format', 'comment', 'protected', 'mirror', 'run', - 'exec', 'env', 'stretch', 'convert', 'dotall', - 'multiline', 'dconf', 'package', 'merge', - 'postmerge', 'action', 'rebuild'} - - return parameters_set - - # возможно, создать отдельный класс для хранения имен имеющихся в системе. - def get_chown_values(self, chown: str): - """Получить значения uid и gid из параметра chown.""" - if chown and ':' in chown: - user_name, group_name = chown.split(':') - import pwd - - try: - if self.base_directory == '/': - uid = pwd.getpwnam(user_name).pw_uid - else: - uid = self.get_uid_from_passwd(user_name) - except (KeyError, TypeError): - self.output.set_error( - "There is no such user in the system: {}". - format(user_name)) - self.output.set_error( - "Wrong 'chown' value '{0}' in the template: {1}". - format(chown, self.template_path)) - return False - - import grp - - try: - if self.base_directory == '/': - gid = grp.getgrnam(group_name).pw_gid - else: - gid = self.get_gid_from_group(group_name) - except (KeyError, TypeError): - self.output.set_error( - "There is no such group in the system: {}". - format(group_name)) - self.output.set_error( - "Wrong 'chown' value '{0}' in the template: {1}". - format(chown, self.template_path)) - return False - return {'uid': uid, 'gid': gid} - else: - self.output.set_error( - "Wrong 'chown' value '{0}' in the template: {1}". - format(chown, self.template_path)) - return False - - def get_uid_from_passwd(self, user_name: str): - """Взять uid из chroot passwd файла.""" - passwd_file_path = os.path.join(self.base_directory, 'etc/passwd') - passwd_dictionary = [] - if os.path.exists(passwd_file_path): - with open(passwd_file_path, 'r') as passwd_file: - for line in passwd_file: - if line.startswith('#'): - continue - passwd_item = tuple(line.split(':')[0:3:2]) - if (len(passwd_item) > 1 and passwd_item[0] - and passwd_item[0]): - passwd_dictionary.append(passwd_item) - passwd_dictionary = dict(passwd_dictionary) - return int(passwd_dictionary[user_name]) - else: - self.output.set_error("passwd file was not found in {}". - format(passwd_file_path)) - return False - - def get_gid_from_group(self, group_name: str): - """Взять gid из chroot group файла.""" - group_file_path = os.path.join(self.base_directory, 'etc/group') - group_dictionary = [] - if os.path.exists(group_file_path): - with open(group_file_path, 'r') as group_file: - for line in group_file: - if line.startswith('#'): - continue - group_item = tuple(line.split(':')[0:3:2]) - if len(group_item) > 1 and group_item[0] and group_item[1]: - group_dictionary.append(group_item) - group_dictionary = dict(group_dictionary) - if group_name in group_dictionary: - return int(group_dictionary[group_name]) - else: - self.output.set_error("'{0}' gid was not found in {1}". - format(group_name, group_file_path)) - else: - self.output.set_error("group file was not found in {}". - format(group_file_path)) - return False - - def chown_directory(self, target_path): + 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, self.chown['uid'], self.chown['gid']) + 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): + 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, self.chmod) + 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): @@ -406,6 +719,26 @@ class TemplateAction: 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 is_vfat(self, path): if self.mounts is None: self.mounts = Mounts() @@ -425,31 +758,43 @@ class TemplateAction: return True return False - -class FormatDetector: - pass + def _clear_parameters(self): + '''Метод для удаления данных о шаблоне директории после + его выполнения.''' + try: + del(self.template_parameters) + except NameError: + pass class DirectoryProcessor: chmod_regex = re.compile(r'\d{3}') def __init__(self, action, datavars_module=Variables(), package='', - output_module=IOModule()): - self.INHERITABLE_PARAMETERS = {'chmod', 'chown', 'autoupdate', - 'env', 'package', 'action'} + output_module=IOModule(), test_mode=False): + self.action = action + self.chroot_path = datavars_module.main.cl_chroot_path + if not test_mode: + self.cl_chroot_path = datavars_module.main.cl_chroot_path + else: + self.cl_chroot_path = datavars_module.test.test_root + self.datavars_module = datavars_module self.output = output_module - self.template_action = TemplateAction(output_module=self.output) + self.template_action = TemplateAction(output_module=self.output, + chroot_path=self.chroot_path) + + self.parameters_set = self.template_action.available_parameters + self.formats_set = self.template_action.available_formats + self.inheritable_parameters =\ + set(TemplateParameters.inheritable_parameters) - self.action = action - self.chroot_path = datavars_module.main.cl_chroot.path - self.parameters_set = self.template_action.get_parameter_names() self.template_engine = TemplateEngine( datavars_module=datavars_module, parameters_set=self.parameters_set ) self.template_paths = (self.datavars_module. - main.cl_template.path.split(',')) + main.cl_template_path.split(',')) self.for_package = package self.processed_packages = [] @@ -464,7 +809,7 @@ class DirectoryProcessor: entries = os.scandir(directory_path) for node in entries: self.walk_directory_tree(node.path, - self.chroot_path, + self.cl_chroot_path, {}, set()) if self.for_package: @@ -491,7 +836,7 @@ class DirectoryProcessor: first_directory_path = os.path.join(base_directory, first_directory) self.walk_directory_tree(first_directory_path, - self.chroot_path, {}, set(), + self.cl_chroot_path, {}, set(), directories_queue=queue) self.processed_packages.append(self.for_package) @@ -551,8 +896,11 @@ class DirectoryProcessor: self.output.console_output.print_default('\n') return except Exception as error: - self.output.set_error('Template error: {}'. - format(str(error))) + self.output.set_error('Template error: {0} \nTemplate: {1},' + ' lineno: {2}'. + format(str(error), + current_directory_path, + error.lineno)) return directory_parameters.update(self.template_engine.parameters) @@ -564,7 +912,8 @@ class DirectoryProcessor: # Если есть параметр path -- меняем текущий путь к целевому # каталогу. if 'path' in directory_parameters: - current_target_path = directory_parameters['path'] + current_target_path = join_paths(self.cl_chroot_path, + directory_parameters['path']) if directory_parameters.get('append', None) != 'skip': current_target_path = os.path.join(current_target_path, @@ -585,7 +934,8 @@ class DirectoryProcessor: "'package' is not defined for {}". format(current_directory_path) ) - return + # считаем наличие параметра package необязательным. + # return elif directory_parameters['package'] != self.for_package: package_name = directory_parameters['package'] if package_name in self.packages_file_paths: @@ -621,25 +971,21 @@ class DirectoryProcessor: # Выполняем действие с директорией. self.output.set_success('Processing directory: {}'. format(current_directory_path)) - self.output.console_output.print_default( - 'Directory parameters: {}\n'. - format(directory_parameters) - ) - self.output.set_info( - 'Actions using template directory. Target: {}'. - format(current_target_path) - ) + + self.template_action.process_template_directory( + current_target_path, + template_path=current_directory_path, + parameters=directory_parameters) # Оставляем только наследуемые параметры. directory_parameters = {name: value for name, value in directory_parameters.items() - if name in self.INHERITABLE_PARAMETERS} - if 'chmod' in directory_parameters: - match = self.chmod_regex.search( - str(directory_parameters['chmod']) - ) - if match: - del(directory_parameters['chmod']) + if name in self.inheritable_parameters} + + if ('chmod' in directory_parameters and + directory_parameters['chmod'].isdigit() and + len(directory_parameters['chmod']) <= 4): + del(directory_parameters['chmod']) if directories_queue: next_node = directories_queue.pop() @@ -656,6 +1002,8 @@ class DirectoryProcessor: for template_name in template_files: template_parameters = {} template_parameters = directory_parameters.copy() + template_path = os.path.join(current_directory_path, template_name) + try: self.template_engine.process_template(template_name, env=current_env.copy()) @@ -664,8 +1012,8 @@ class DirectoryProcessor: format(template_name)) continue except Exception as error: - self.output.set_error('Template error: {}'. - format(str(error))) + self.output.set_error('Template error: {0} in template {1}'. + format(str(error), template_path)) continue template_parameters.update(self.template_engine.parameters) @@ -683,12 +1031,11 @@ class DirectoryProcessor: if self.for_package: if 'package' not in template_parameters: self.output.set_warning("'package' is not defined for {}". - format(template_name)) - continue + format(template_path)) + # считаем параметр package необязательным. + # continue elif template_parameters['package'] != self.for_package: package_name = template_parameters['package'] - template_path = os.path.join(current_directory_path, - template_name) if package_name in self.packages_file_paths: self.packages_file_paths[package_name].append( template_path @@ -722,22 +1069,30 @@ class DirectoryProcessor: template_name = template_parameters['name'] if 'path' in template_parameters: - target_file_path = os.path.join(template_parameters['path'], - template_name) + target_file_path = join_paths(self.cl_chroot_path, + template_parameters['path'], + template_name) else: - target_file_path = os.path.join(current_target_path, - template_name) + target_file_path = join_paths(current_target_path, + template_name) + + if ('format' in template_parameters and + template_parameters['format'] not in self.formats_set): + self.output.set_error( + "Unknown parameter '{0}' in template: {1}". + format(template_parameters['format'], + target_file_path) + ) + continue # Выполняем действие с использованием шаблона. self.output.set_success('Processing template: {}...'. format(template_name)) - self.output.console_output.print_default( - 'With parameters: {}\n'. - format(template_parameters) - ) - self.output.set_info('Target file: {}.\nTemplate text:'. - format(target_file_path)) - self.output.console_output.print_default(template_text + '\n\n') + self.template_action.process_template_file( + target_file_path, + template_path, + template_text=template_text, + parameters=template_parameters) # проходимся далее по директориям. for directory_path in template_directories: diff --git a/calculate/utils/device.py b/calculate/utils/device.py index 6527639..b343908 100644 --- a/calculate/utils/device.py +++ b/calculate/utils/device.py @@ -47,7 +47,8 @@ def count_partitions(device_name): if not syspath: return 0 device_name = os.path.basename(syspath) - return len([x for x in sysfs.listdir(syspath) if x.startswith(device_name)]) + return len([x for x in sysfs.listdir(syspath) + if x.startswith(device_name)]) def get_lspci_output(filter_name=None, short_info=False): @@ -616,16 +617,3 @@ devfs = DevFS() udev = Udev(UdevAdmCommand()) lvm = Lvm(LvmCommand()) raid = RAID(MdadmCommand()) - - -if __name__ == '__main__': - print('FIND DEVICE BY PARTITION:') - print(find_device_by_partition('/dev/nvme0n1p4')) - print('LSPCI TEST:') - pprint(get_lspci_output()) - print('GET PHYSICAL EXTENT SIZE:') - lvm_obj = Lvm(LvmCommand()) - print(lvm_obj.get_physical_extent_size()) - print('GET RUN COMMANDS:') - for command in files.get_run_commands(with_pid=True): - print(command) diff --git a/calculate/utils/files.py b/calculate/utils/files.py index 92f8720..522833c 100644 --- a/calculate/utils/files.py +++ b/calculate/utils/files.py @@ -286,6 +286,16 @@ def join_paths(*paths): return output_path +def read_link(file_path): + try: + if path.exists(file_path): + return os.readlink(file_path) + except (OSError, IOError) as error: + mod, lineno = get_traceback_caller(*sys.exc_info()) + FilesError("link read error, {}({}:{})". + format(str(error), mod, lineno)) + + def read_file(file_path): try: if path.exists(file_path): @@ -293,10 +303,8 @@ def read_file(file_path): return opened_file.read() except (OSError, IOError) as error: mod, lineno = get_traceback_caller(*sys.exc_info()) - sys.stderr.write("WARNING: file read error, {}({}:{})\n". - format(str(error), mod, lineno)) - sys.stderr.flush() - return '' + FilesError("file read error, {0}({1}:{2})". + format(str(error), mod, lineno)) def write_file(file_path): diff --git a/calculate/utils/mount.py b/calculate/utils/mount.py index 61c6516..9a3d127 100644 --- a/calculate/utils/mount.py +++ b/calculate/utils/mount.py @@ -323,8 +323,3 @@ class Btrfs: @compression.setter def compression(self, value): self.set_compression('', value) - - -if __name__ == '__main__': - print('GET CHILD MOUNTS TEST:') - print(get_child_mounts('/home')) diff --git a/calculate/utils/package.py b/calculate/utils/package.py new file mode 100644 index 0000000..3229b63 --- /dev/null +++ b/calculate/utils/package.py @@ -0,0 +1,298 @@ +# vim: fileencoding=utf-8 +# +import os +import re +import glob +from collections import OrderedDict +from ..templates.format.contents_format import ContentsFormat +from .files import read_file, read_link, join_paths, FilesError +import hashlib + + +class PackageError(Exception): + pass + + +class PackageAtomError(Exception): + pass + + +class PackageAtom: + atom_regex = re.compile(r'''(?P[^\s/]*)/ + (?P[^\s:]*) + (?P:\S*)? + (?P(?:\s+\S*)*) + ''', re.VERBOSE) + + def __init__(self, pkg_path='/var/db/pkg', + chroot_path='/'): + self.chroot_path = chroot_path + + if chroot_path != '/': + self.pkg_path = join_paths(chroot_path, pkg_path) + else: + self.pkg_path = pkg_path + + self.package_atom = '' + self._atom_dictionary = {} + + def parse_package_parameter(self, package_atom, + add_slot=False, add_uses=False): + self.package_atom = package_atom + self._atom_dictionary = {} + + parsing_result = self.atom_regex.search(package_atom) + + if not parsing_result or parsing_result.string != package_atom: + raise PackageAtomError("'package' parameter value '{}' is not" + " correct".format(package_atom)) + + if 'category' in parsing_result.groupdict(): + self._atom_dictionary['category'] = parsing_result.groupdict( + )['category'] + + if 'name' in parsing_result.groupdict(): + self._atom_dictionary['name'] = parsing_result.groupdict()['name'] + + self._check_package_existance() + + if ('slot' in parsing_result.groupdict() and + parsing_result.groupdict()['slot'] and + parsing_result.groupdict()['slot'] != ':'): + print('slot value is correct') + self._atom_dictionary['slot'] = parsing_result.groupdict( + )['slot'][1:] + elif add_slot: + self._atom_dictionary['slot'] = self._get_slot_value() + + if ('uses' in parsing_result.groupdict() and + parsing_result.groupdict()['uses']): + self._atom_dictionary['uses'] = [] + uses = parsing_result.groupdict()['uses'].strip().split(' ') + for use_flag in uses: + self._atom_dictionary['uses'].append(use_flag.strip()) + + elif add_uses: + self._atom_dictionary['uses'] = self._get_use_flags_value() + + def _check_package_existance(self, package_atom=''): + if package_atom: + self.parse_package_parameter(package_atom) + return True + elif (self._atom_dictionary['category'] and + self._atom_dictionary['name']): + glob_result = glob.glob( + '{0}/{1}/{2}*/CONTENTS'.format(self.pkg_path, + self._atom_dictionary['category'], + self._atom_dictionary['name'])) + if not glob_result: + raise PackageAtomError("Package from 'package' parameter value" + " '{}' does not exist".format( + self.package_atom)) + elif len(glob_result) == 1: + contents_path = next(iter(glob_result)) + self._atom_dictionary['name'] = contents_path.split('/')[-2] + self._atom_dictionary['contents'] = contents_path + else: + raise PackageAtomError("'package' parameter value '{}' matches" + " multiple installed packages".format( + self.package_atom)) + + def _get_slot_value(self): + contents_path = self._atom_dictionary['contents'] + slot_path = os.path.join(os.path.dirname(contents_path), 'SLOT') + try: + return read_file(slot_path).strip('\n') + except FilesError: + raise PackageAtomError("could not read slot value for" + " 'package': {}".format(self.package_atom)) + + def _get_use_flags_value(self): + contents_path = self._atom_dictionary['contents'] + use_path = os.path.join(os.path.dirname(contents_path), 'USE') + try: + return read_file(use_path).strip('\n').split(' ') + except FilesError: + raise PackageAtomError("could not read use flags for 'package'" + " parameter: {}".format(self.package_atom)) + + def _get_category_packages(self, category): + for path in glob.glob('{0}/{1}/*/CONTENTS'.format(self.pkg_path, + category)): + yield path + + def get_file_package(self, file_path, with_slot=False, with_uses=False): + if self.chroot_path != '/' and file_path.startswith(self.chroot_path): + file_path = file_path[len(self.chroot_path):] + + for category in os.listdir(self.pkg_path): + for contents_path in self._get_category_packages(category): + try: + with open(contents_path, 'r') as contents_file: + for file_line in contents_file.readlines(): + contents_name = file_line.split(' ')[1].strip() + if contents_name == file_path: + package = '/'.join(os.path.dirname( + contents_path).split('/')[-2:]) + return package + except (OSError, IOError): + continue + else: + raise PackageAtomError("The file does not belong to any package") + + @property + def atom_dictionary(self): + return self._atom_dictionary + + @property + def category(self): + if 'category' in self._atom_dictionary: + return self._atom_dictionary['category'] + else: + return False + + @property + def name(self): + if 'name' in self._atom_dictionary: + return self._atom_dictionary['name'] + else: + return False + + @property + def slot(self): + if 'slot' in self._atom_dictionary: + return self._atom_dictionary['slot'] + else: + return False + + @property + def uses(self): + if 'uses' in self._atom_dictionary: + return self._atom_dictionary['uses'] + else: + return False + + +class Package: + re_cfg = re.compile(r'/\._cfg\d{4}_') + + def __init__(self, package_atom, pkg_path='/var/db/pkg', chroot_path='/'): + self.chroot_path = chroot_path + + self.contents_file_path = os.path.join(pkg_path, package_atom, + 'CONTENTS') + self.contents_dictionary = OrderedDict() + + if chroot_path != '/': + self.contents_file_path = join_paths(chroot_path, + self.contents_file_path) + os.path.exists(self.contents_file_path) + self.read_contents_file() + + def remove_cfg_prefix(self, file_name): + return self.re_cfg.sub('/', file_name) + + def remove_chroot_path(self, file_name): + if self.chroot_path != '/' and file_name.startswith(self.chroot_path): + return file_name[len(self.chroot_path):] + else: + return file_name + + def read_contents_file(self): + try: + contents_text = read_file(self.contents_file_path) + except FilesError as error: + raise PackageError(str(error)) + if contents_text: + contents_format = ContentsFormat(contents_text, + template_parser=False) + self.contents_dictionary = contents_format._document_dictionary + return True + else: + return False + + def write_contents_file(self): + pass + + def render_contents_file(self): + contents_format = ContentsFormat('', template_parser=False) + contents_format._document_dictionary = self.contents_dictionary + return contents_format.get_document_text() + + def add_dir(self, file_name): + file_name = self.remove_cfg_prefix(file_name) + file_name = self.remove_chroot_path(file_name) + + if (file_name != '/' and + (file_name not in self.contents_dictionary + or self.contents_dictionary[file_name]['type'] != 'dir')): + self.add_dir(os.path.dirname(file_name)) + contents_item = OrderedDict({'type': 'dir'}) + self.contents_dictionary[file_name] = contents_item + + def add_sym(self, file_name): + file_name = self.remove_cfg_prefix(file_name) + + real_path = file_name + file_name = self.remove_chroot_path(file_name) + + if real_path == file_name: + real_path = join_paths(self.chroot_path, file_name) + + 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), + 'mtime': mtime}) + except FilesError as error: + raise PackageError(str(error)) + + self.contents_dictionary[file_name] = contents_item + + def add_obj(self, file_name): + file_name = self.remove_cfg_prefix(file_name) + + real_path = file_name + file_name = self.remove_chroot_path(file_name) + + 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)) + + contents_item = OrderedDict({'type': 'obj', + 'md5': hashlib.md5(file_text).hexdigest(), + 'mtime': + str(int(os.lstat(real_path).st_mtime))}) + self.contents_dictionary[file_name] = contents_item + + def add_file(self, file_name): + if file_name != '/': + real_path = file_name + file_name = self.remove_chroot_path(file_name) + + if real_path != file_name: + real_path = join_paths(self.chroot_path, file_name) + if os.path.isdir(real_path): + self.add_dir(file_name) + elif os.path.islink(real_path): + self.add_link(file_name) + elif os.path.isfile(real_path): + self.add_obj(file_name) + + def modify_contents_item(self, file_name, item_value): + pass + + def remove_file(self, file_name): + pass + + def remove_empty_directories(self): + pass + + def check_file_md5(self, file_path): + pass diff --git a/pytest.ini b/pytest.ini index 1de1b1a..cb1f98e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,6 +5,7 @@ markers = base: marker for running tests for base format class. bind: marker for running tests for bind format. compiz: marker for running tests for compiz format. + contents: marker for running tests for contents format. diff: marker for running test for diff format. dovecot: marker for running tests for devecot format. json: marker for running tests for json format. @@ -20,8 +21,11 @@ markers = xml_xfce: marker for running tests for xml xfce format. xml_gconf: marker for running tests for xml gconf format. - files: marker for running tests for calculate.utils.files module. + files_utils: marker for running tests for calculate.utils.files module. + package_utils: marker for running tests for calculate.utils.contents module. + vars: marker for running tests for datavars template_engine: marker for running tests for TemplateEngine. directory_processor: marker for running tests for DirectoryProcessor. template_action: marker for running tests for TemplateAction. + template_parameters: marker for running test for TemplateParameters. diff --git a/tests/templates/format/test_contents.py b/tests/templates/format/test_contents.py new file mode 100644 index 0000000..fbf7d8a --- /dev/null +++ b/tests/templates/format/test_contents.py @@ -0,0 +1,82 @@ +import pytest +from collections import OrderedDict +from calculate.templates.format.contents_format import ContentsFormat + + +@pytest.mark.contents +class TestParsingMethods: + def test_if_input_document_contains_a_few_lines_without_any_action_symbols__the_initialised_object_contains_correct_dictionary(self): + document_text = ''' + obj /usr/bin/vim 30acc0f256e11c1ecdb1bd80b688d238 1573538056 + sym /usr/share/bash-completion/completions/ex -> vim 1573538054 + dir /usr/share/bash-completion/completions + ''' + + result = OrderedDict({('', '/usr/bin/vim'): + [('obj', '30acc0f256e11c1ecdb1bd80b688d238', + '1573538056')], + ('', '/usr/share/bash-completion/completions/ex'): + [('sym', 'vim', '1573538054')], + ('', '/usr/share/bash-completion/completions'): + [('dir',)]}) + + contents_object = ContentsFormat(document_text) + assert result == contents_object._document_dictionary + + def test_if_input_document_contains_a_few_lines_with_some_action_symbols__the_initialised_object_contains_correct_dictionary(self): + document_text = ''' + !obj /usr/bin/vim 30acc0f256e11c1ecdb1bd80b688d238 1573538056 + -sym /usr/share/bash-completion/completions/ex -> vim 1573538054 + !dir /usr/share/bash-completion/completions + ''' + + result = OrderedDict({('!', '/usr/bin/vim'): + [('obj', '30acc0f256e11c1ecdb1bd80b688d238', + '1573538056')], + ('-', '/usr/share/bash-completion/completions/ex'): + [('sym', 'vim', '1573538054')], + ('!', '/usr/share/bash-completion/completions'): + [('dir',)]}) + + contents_object = ContentsFormat(document_text) + assert result == contents_object._document_dictionary + + def test_if_template_parser_flag_is_set_False__the_initialized_object_contains_correct_dictionary_for_contents_util_module(self): + document_text = ''' + obj /usr/bin/vim 30acc0f256e11c1ecdb1bd80b688d238 1573538056 + sym /usr/share/bash-completion/completions/ex -> vim 1573538054 + dir /usr/share/bash-completion/completions + ''' + + result = OrderedDict({'/usr/bin/vim': + OrderedDict( + {'type': 'obj', + 'md5': '30acc0f256e11c1ecdb1bd80b688d238', + 'mtime': '1573538056'}), + '/usr/share/bash-completion/completions/ex': + OrderedDict( + {'type': 'sym', + 'target': 'vim', + 'mtime': '1573538054'}), + '/usr/share/bash-completion/completions': + OrderedDict({'type': 'dir'})}) + + contents_object = ContentsFormat(document_text, template_parser=False) + assert result == contents_object._document_dictionary + + def test_joining_documents_1(self): + with open('./tests/templates/format/testfiles/contents_original', 'r') as original_file: + original_text = original_file.read() + contents_original_object = ContentsFormat(original_text) + + with open('./tests/templates/format/testfiles/contents_template', 'r') as template_file: + template_text = template_file.read() + contents_template_object = ContentsFormat(template_text, + ignore_comments=True) + + contents_original_object.join_template(contents_template_object) + + with open('./tests/templates/format/testfiles/contents_result', 'r') as result_file: + result_text = result_file.read() + + assert contents_original_object.get_document_text() == result_text diff --git a/tests/templates/format/testfiles/contents_original b/tests/templates/format/testfiles/contents_original new file mode 100644 index 0000000..85bbfb3 --- /dev/null +++ b/tests/templates/format/testfiles/contents_original @@ -0,0 +1,18 @@ +dir /usr +dir /usr/bin +sym /usr/bin/rview -> vim 1573538053 +sym /usr/bin/rvim -> vim 1573538053 +obj /usr/bin/vim 30acc0f256e11c1ecdb1bd80b688d238 1573538056 +sym /usr/bin/vimdiff -> vim 1573538053 +dir /usr/share +dir /urs/share/dir +obj /urs/share/dir/file 4580e5268ddf10175cdd86f87f481d71 1560504013 +dir /usr/share/applications +dir /usr/share/bash-completion +dir /usr/share/bash-completion/completions +sym /usr/share/bash-completion/completions/ex -> vim 1573538054 +sym /usr/share/bash-completion/completions/rview -> vim 1573538054 +sym /usr/share/bash-completion/completions/rvim -> vim 1573538054 +sym /usr/share/bash-completion/completions/vi -> vim 1573538054 +obj /usr/share/bash-completion/completions/vim 49aa29933e92bb54d84eb6efc5cdd801 1573538054 +sym /usr/share/bash-completion/completions/vimdiff -> vim 1573538054 diff --git a/tests/templates/format/testfiles/contents_result b/tests/templates/format/testfiles/contents_result new file mode 100644 index 0000000..ce77d14 --- /dev/null +++ b/tests/templates/format/testfiles/contents_result @@ -0,0 +1,22 @@ +dir /usr +dir /usr/bin +sym /usr/bin/rview -> vim 1573538053 +sym /usr/bin/rvim -> vim 1573538053 +obj /usr/bin/vim 30acc0f256e11c1ecdb1bd80b688d238 1573538056 +sym /usr/bin/vimdiff -> vim 1573538053 +dir /usr/share +dir /usr/share/applications +dir /usr/share/bash-completion +dir /usr/share/bash-completion/completions +sym /usr/share/bash-completion/completions/ex -> vim 1573538054 +sym /usr/share/bash-completion/completions/rview -> vim 1573538054 +sym /usr/share/bash-completion/completions/rvim -> vim 1573538054 +sym /usr/share/bash-completion/completions/vi -> vi 1573538054 +obj /usr/share/bash-completion/completions/vim 49aa29933e92bb54d84eb6efc5cdd801 1573538054 +sym /usr/share/bash-completion/completions/vimdiff -> vim 1573538054 +dir /etc +dir /etc/env.d +obj /etc/env.d/99editor 835da024d5f3c7a862934df592cdc9fe 1574417039 +sym /usr/share/bash-completion/completions/view -> vim 1573538054 +dir /etc/vim +obj /etc/vim/vimrc.local ed7cd3cef611ff49b12093b93fed59be 1574417039 diff --git a/tests/templates/format/testfiles/contents_template b/tests/templates/format/testfiles/contents_template new file mode 100644 index 0000000..d23ec99 --- /dev/null +++ b/tests/templates/format/testfiles/contents_template @@ -0,0 +1,9 @@ +dir /etc +dir /etc/env.d +obj /etc/env.d/99editor 835da024d5f3c7a862934df592cdc9fe 1574417039 +sym /usr/share/bash-completion/completions/vi -> vi 1573538054 +sym /usr/share/bash-completion/completions/view -> vim 1573538054 +!dir /urs/share/dir +!obj /urs/share/dir/file 4580e5268ddf10175cdd86f87f481d71 1560504013 +dir /etc/vim +obj /etc/vim/vimrc.local ed7cd3cef611ff49b12093b93fed59be 1574417039 diff --git a/tests/templates/test_directory_processor.py b/tests/templates/test_directory_processor.py index d424bf7..431ab34 100644 --- a/tests/templates/test_directory_processor.py +++ b/tests/templates/test_directory_processor.py @@ -1,4 +1,6 @@ import pytest +import time +import os from calculate.templates.template_processor import DirectoryProcessor from calculate.templates.template_engine import Variables @@ -24,29 +26,35 @@ merge = Variables({'var_1': 674, 'calculate_domains': 'lists.calculate-linux.org', 'ip_value': '127.0.0.0/8'}) -cl_template = Variables({ - 'path': - 'tests/templates/testfiles/test_dir_1,tests/templates/testfiles/test_dir_2' - }) +main = Variables({'cl_template_path': + ('tests/templates/testfiles/template_dir_1,' + 'tests/templates/testfiles/template_dir_2'), + 'cl_chroot_path': '/'}) -cl_chroot = Variables({'path': '/etc'}) - -main = Variables({'cl_template': cl_template, - 'cl_chroot': cl_chroot}) +test = Variables({'test_root': os.path.join(os.getcwd(), + 'tests/templates/testfiles')}) datavars = Variables({'install': install, 'merge': merge, 'variables': variables, 'main': main, + 'test': test, 'custom': Variables()}) @pytest.mark.directory_processor class TestDirectoryProcessor: def test_just_for_debug(self): + start_time = time.time() try: - dir_processor = DirectoryProcessor('install', datavars_module=datavars, - package='package_1') + dir_processor = DirectoryProcessor( + 'install', + datavars_module=datavars, + package='xfce-extra/xfce4-screenshooter', + test_mode=True) dir_processor.process_template_directories() except Exception as error: pytest.fail('Unexpected exception: {}'.format(str(error))) + + # print('time: {}'.format(time.time() - start_time)) + # assert False diff --git a/tests/templates/test_template_action.py b/tests/templates/test_template_action.py index ec25bed..3e4c8c0 100644 --- a/tests/templates/test_template_action.py +++ b/tests/templates/test_template_action.py @@ -1,8 +1,52 @@ import pytest -from calculate.templates.template_processor import TemplateAction -from calculate.templates.template_engine import Variables +import os +from calculate.templates.template_processor import TemplateAction,\ + TemplateParameters, DIR + + +template_action = TemplateAction() + +TEST_DIRECTORY_PATH = os.path.join(os.getcwd(), 'tests/templates/testfiles') @pytest.mark.template_action class TestTemplateAction: - pass + def test_chown_directory(self): + pass + + def test_chmod_directory(self): + pass + + def test_create_directory(self): + path_to_create = os.path.join(TEST_DIRECTORY_PATH, + 'dir_tests/new_dir/new_subdir') + template_action.template_parameters = TemplateParameters( + {'chown': + '{0}:{1}'.format(os.getuid(), + os.getgid())}, + DIR) + template_action._create_directory(target_path=path_to_create) + assert os.path.exists(path_to_create) + + def test_link_directory(self): + path_to_source = os.path.join(TEST_DIRECTORY_PATH, + 'dir_tests/new_dir/new_subdir') + path_to_link = os.path.join(TEST_DIRECTORY_PATH, + 'dir_tests/new_dir/new_subdir_link') + template_action.source = path_to_source + template_action._link_directory(path_to_link) + + template_action.source = False + assert os.path.exists(path_to_link) and os.path.islink(path_to_link) + + def test_remove_directory(self): + directory_to_remove = os.path.join(TEST_DIRECTORY_PATH, + 'dir_tests/dir') + link_to_remove = os.path.join(TEST_DIRECTORY_PATH, + 'dir_tests/new_dir/new_subdir_link') + + template_action._remove_directory(link_to_remove) + assert not os.path.exists(link_to_remove) + + template_action._remove_directory(directory_to_remove) + assert not os.path.exists(directory_to_remove) diff --git a/tests/templates/test_template_parameters.py b/tests/templates/test_template_parameters.py new file mode 100644 index 0000000..e3a815e --- /dev/null +++ b/tests/templates/test_template_parameters.py @@ -0,0 +1,137 @@ +import pytest +import os +from calculate.templates.template_processor import TemplateParameters, DIR,\ + FILE, IncorrectParameter,\ + TemplateAction +from calculate.utils.files import join_paths +from pprint import pprint + + +CHROOT_PATH = os.path.join(os.getcwd(), 'tests/templates/testfiles') + + +@pytest.mark.template_parameters +class TestTemplateParameters: + def test_if_TemplateParameters_object_is_initialized_accoding_to_dictionary_of_correct_template_parameters__the_TemplateParameters_object_contains_processed_parameters_as_its_attributes_including_default_values(self): + parameters = {'append': 'join', + 'chmod': '600', + 'force': True} + template_parameters = TemplateParameters(parameters, DIR) + assert (template_parameters.append == 'join' and + template_parameters.chmod == 0o600 and + template_parameters.force and + not template_parameters.autoupdate) + + def test_if_TemplateParameters_object_is_intialized_using_dictionary_with_append_parameter__a_value_of_the_parameter_will_be_checked(self): + parameters = {'append': 'join'} + # Для получения множества доступных значений параметра append. + TemplateAction() + try: + template_parameters = TemplateParameters(parameters, FILE) + assert template_parameters.append == 'join' + except Exception as error: + pytest.fail('Unexpected exception: {0}'. + format(str(error))) + + def test_if_TemplateParameters_object_is_intialized_using_dictionary_with_correct_source_parameter__the_object_will_be_initialized_successfully(self): + parameters = {'source': '/test_dir_1/file.test'} + + try: + template_parameters = TemplateParameters(parameters, FILE, + chroot_path=CHROOT_PATH) + assert template_parameters.source == join_paths( + CHROOT_PATH, + '/test_dir_1/file.test') + except Exception as error: + pytest.fail('Unexpected exception: {0}'. + format(str(error))) + + def test_if_TemplateParameters_object_is_intialized_as_dir_parameters_object_using_correct_source_parameter_with_append_link__the_object_will_be_initialized_successfully(self): + parameters = {'append': 'link', 'source': '/test_dir_1'} + + try: + template_parameters = TemplateParameters(parameters, DIR, + chroot_path=CHROOT_PATH) + assert template_parameters.source == join_paths(CHROOT_PATH, + '/test_dir_1') + except Exception as error: + pytest.fail('Unexpected exception: {0}'. + format(str(error))) + + def test_if_TemplateParameters_object_is_intialized_using_source_parameter_with_unexisting_file_path__the_initialization_of_the_object_will_be_failed(self): + parameters = {'source': '/test_dir_1/unexisted_file.test'} + with pytest.raises(IncorrectParameter): + template_parameters = TemplateParameters(parameters, FILE) + + def test_if_TemplateParameters_object_is_intialized_as_dir_parameters_object_using_source_parameter_but_without_append_link__the_initialization_of_the_object_will_be_failed(self): + parameters = {'source': '/test_dir_1/file.test'} + with pytest.raises(IncorrectParameter): + template_parameters = TemplateParameters(parameters, DIR, + chroot_path=CHROOT_PATH) + + def test_if_TemplateParameters_object_is_intialized_using_dictionary_with_correct_force_parameter__the_object_will_be_initialized_successfully(self): + parameters = {'force': True} + try: + template_parameters = TemplateParameters(parameters, FILE) + assert template_parameters.force + except Exception as error: + pytest.fail('Unexpected exception: {0}'. + format(str(error))) + + def test_if_TemplateParameters_object_is_intialized_using_dictionary_with_incorrect_force_parameter__the_initialization_of_the_object_will_be_failed(self): + parameters = {'force': 'value'} + with pytest.raises(IncorrectParameter): + template_parameters = TemplateParameters(parameters, FILE) + + def test_if_TemplateParameters_object_is_intialized_using_dictionary_with_autoupdate_parameter__a_value_of_the_parameter_will_be_checked(self): + parameters = {'autoupdate': True} + try: + template_parameters = TemplateParameters(parameters, FILE) + assert template_parameters.autoupdate + except Exception as error: + pytest.fail('Unexpected exception: {0}'. + format(str(error))) + + def test_if_TemplateParameters_object_is_intialized_using_dictionary_with_incorrect_autoupdate_parameter__the_initialization_of_the_object_will_be_failed(self): + parameters = {'autoupdate': 'value'} + with pytest.raises(IncorrectParameter): + template_parameters = TemplateParameters(parameters, FILE) + + def test_if_TemplateParameters_object_is_intialized_using_dictionary_with_correct_chown_parameter__the_object_will_be_initialized_successfully(self): + parameters = {'chown': 'root:root'} + try: + template_parameters = TemplateParameters(parameters, FILE) + assert template_parameters.chown == {'uid': 0, 'gid': 0} + except Exception as error: + pytest.fail('Unexpected exception: {0}'. + format(str(error))) + + def test_if_TemplateParameters_object_is_intialized_using_dictionary_with_correct_chown_parameter_in_its_digital_form__the_object_will_be_initialized_successfully(self): + uid = os.getuid() + gid = os.getgid() + parameters = {'chown': '{0}:{1}'.format(uid, gid)} + try: + template_parameters = TemplateParameters(parameters, FILE) + assert template_parameters.chown == {'uid': uid, 'gid': gid} + except Exception as error: + pytest.fail('Unexpected exception: {0}'. + format(str(error))) + + def test_if_TemplateParameters_object_is_intialized_using_dictionary_with_incorrect_chown_parameter__the_initialization_of_the_object_will_be_failed(self): + parameters = {'chown': 'wrong_user_name:wrong_group_name'} + with pytest.raises(IncorrectParameter): + template_parameters = TemplateParameters(parameters, FILE) + + def test_if_TemplateParameters_object_is_intialized_using_dictionary_with_correct_chmod_parameter__the_object_will_be_initialized_successfully(self): + try: + parameters = {'chmod': 'rw-r--r--'} + template_parameters = TemplateParameters(parameters, FILE) + assert template_parameters.chmod == 0o644 + del(template_parameters) + + parameters = {'chmod': '600'} + template_parameters = TemplateParameters(parameters, FILE) + assert template_parameters.chmod == 0o600 + except Exception as error: + pytest.fail('Unexpected exception: {0}'. + format(str(error))) diff --git a/tests/templates/testfiles/template_dir_1/test_dir/.calculate_directory b/tests/templates/testfiles/template_dir_1/test_dir/.calculate_directory new file mode 100644 index 0000000..b52dbfc --- /dev/null +++ b/tests/templates/testfiles/template_dir_1/test_dir/.calculate_directory @@ -0,0 +1,2 @@ +{% calculate append = 'skip', name = 'test_root', chown = 'divanov:guest', +action = 'install', path = test.test_root %} diff --git a/tests/templates/testfiles/test_dir_1/test_dir/file.conf b/tests/templates/testfiles/template_dir_1/test_dir/file.conf similarity index 56% rename from tests/templates/testfiles/test_dir_1/test_dir/file.conf rename to tests/templates/testfiles/template_dir_1/test_dir/file.conf index 3129485..7e30f05 100644 --- a/tests/templates/testfiles/test_dir_1/test_dir/file.conf +++ b/tests/templates/testfiles/template_dir_1/test_dir/file.conf @@ -1,4 +1,8 @@ -{% calculate format = 'kde', action = 'install', package = 'kde' -%} +{% calculate format = 'kde', action = 'install', +package = 'xfce-base/xfce4-session' -%} +{% set file_path = test.test_root + '/test_root/conf_dir'%} +{% calculate path = file_path %} +{% calculate merge = 'xfce-base/xfconf' %} [section][parts][of][section name] parameter 1 = {{ variables.variable_1 }} diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir1/.calculate_directory b/tests/templates/testfiles/template_dir_1/test_dir/subdir1/.calculate_directory similarity index 52% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir1/.calculate_directory rename to tests/templates/testfiles/template_dir_1/test_dir/subdir1/.calculate_directory index aaa8da8..a0157a6 100644 --- a/tests/templates/testfiles/test_dir_1/test_dir/subdir1/.calculate_directory +++ b/tests/templates/testfiles/template_dir_1/test_dir/subdir1/.calculate_directory @@ -1,3 +1,4 @@ {% calculate name = 'subdir_1_folder', env='merge', chmod = 'rwxr-xr-x', action = 'install' -%} -{% calculate package = 'xfce', merge = 'package_2' -%} +{% calculate package = 'xfce-base/xfce4-panel', merge = 'xfce-base/xfce4-session' -%} +{% calculate append = 'join' %} {% calculate not install.boolean -%} diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir1/template1/.calculate_directory b/tests/templates/testfiles/template_dir_1/test_dir/subdir1/template1/.calculate_directory similarity index 100% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir1/template1/.calculate_directory rename to tests/templates/testfiles/template_dir_1/test_dir/subdir1/template1/.calculate_directory diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir1/template1/template_1 b/tests/templates/testfiles/template_dir_1/test_dir/subdir1/template1/template_1 similarity index 100% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir1/template1/template_1 rename to tests/templates/testfiles/template_dir_1/test_dir/subdir1/template1/template_1 diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir1/template2/.calculate_directory b/tests/templates/testfiles/template_dir_1/test_dir/subdir1/template2/.calculate_directory similarity index 100% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir1/template2/.calculate_directory rename to tests/templates/testfiles/template_dir_1/test_dir/subdir1/template2/.calculate_directory diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir1/template2/a_template_2.xml b/tests/templates/testfiles/template_dir_1/test_dir/subdir1/template2/a_template_2.xml similarity index 91% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir1/template2/a_template_2.xml rename to tests/templates/testfiles/template_dir_1/test_dir/subdir1/template2/a_template_2.xml index f9ff917..2a85eef 100644 --- a/tests/templates/testfiles/test_dir_1/test_dir/subdir1/template2/a_template_2.xml +++ b/tests/templates/testfiles/template_dir_1/test_dir/subdir1/template2/a_template_2.xml @@ -1,4 +1,5 @@ {% calculate format='xml_xfce', mirror -%} +{% calculate merge='xfce-base/xfconf' %} {% save custom.group.parameter = 'DoubleClickTime' -%} diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir1/template2/b_template_2.xml b/tests/templates/testfiles/template_dir_1/test_dir/subdir1/template2/b_template_2.xml similarity index 100% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir1/template2/b_template_2.xml rename to tests/templates/testfiles/template_dir_1/test_dir/subdir1/template2/b_template_2.xml diff --git a/tests/templates/testfiles/template_dir_1/test_dir/subdir2/.calculate_directory b/tests/templates/testfiles/template_dir_1/test_dir/subdir2/.calculate_directory new file mode 100644 index 0000000..cfd3f3f --- /dev/null +++ b/tests/templates/testfiles/template_dir_1/test_dir/subdir2/.calculate_directory @@ -0,0 +1,4 @@ +{% calculate name = 'important_dir', path = '/etc/folder/in_folder', action = 'install', +package = 'media-sound/alsa-utils' -%} +{% calculate install.version > 1.4 %} +{% calculate merge.version < 1.2 -%} diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir2/template3/.calculate_directory b/tests/templates/testfiles/template_dir_1/test_dir/subdir2/template3/.calculate_directory similarity index 100% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir2/template3/.calculate_directory rename to tests/templates/testfiles/template_dir_1/test_dir/subdir2/template3/.calculate_directory diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir2/template3/template_3.conf b/tests/templates/testfiles/template_dir_1/test_dir/subdir2/template3/template_3.conf similarity index 87% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir2/template3/template_3.conf rename to tests/templates/testfiles/template_dir_1/test_dir/subdir2/template3/template_3.conf index bfc6cc9..50ecb04 100644 --- a/tests/templates/testfiles/test_dir_1/test_dir/subdir2/template3/template_3.conf +++ b/tests/templates/testfiles/template_dir_1/test_dir/subdir2/template3/template_3.conf @@ -1,5 +1,5 @@ {% calculate format = 'kde', path = '/etc/folder/in_folder', name = 'filename.conf' -%} -{% calculate merge = 'any, xfce' -%} +{% calculate merge = 'xfce-base/xfce4-panel' -%} # KDE or Plasma config file. # Part from Plasma [PlasmaViews][Panel 69][Horizontal1024] diff --git a/tests/templates/testfiles/template_dir_1/test_dir/subdir3/.calculate_directory b/tests/templates/testfiles/template_dir_1/test_dir/subdir3/.calculate_directory new file mode 100644 index 0000000..f45a204 --- /dev/null +++ b/tests/templates/testfiles/template_dir_1/test_dir/subdir3/.calculate_directory @@ -0,0 +1,3 @@ +{% calculate name = 'directory', path = '/etc/important_dir', +package = 'xfce-base/xfconf' %} +{% calculate action = 'install' %} diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir3/template_4/.calculate_directory b/tests/templates/testfiles/template_dir_1/test_dir/subdir3/template_4/.calculate_directory similarity index 100% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir3/template_4/.calculate_directory rename to tests/templates/testfiles/template_dir_1/test_dir/subdir3/template_4/.calculate_directory diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir3/template_4/template_file.conf b/tests/templates/testfiles/template_dir_1/test_dir/subdir3/template_4/template_file.conf similarity index 100% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir3/template_4/template_file.conf rename to tests/templates/testfiles/template_dir_1/test_dir/subdir3/template_4/template_file.conf diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir3/template_5/.calculate_directory b/tests/templates/testfiles/template_dir_1/test_dir/subdir3/template_5/.calculate_directory similarity index 100% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir3/template_5/.calculate_directory rename to tests/templates/testfiles/template_dir_1/test_dir/subdir3/template_5/.calculate_directory diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir3/template_5/template_file.conf b/tests/templates/testfiles/template_dir_1/test_dir/subdir3/template_5/template_file.conf similarity index 72% rename from tests/templates/testfiles/test_dir_1/test_dir/subdir3/template_5/template_file.conf rename to tests/templates/testfiles/template_dir_1/test_dir/subdir3/template_5/template_file.conf index 54b4fb4..a40df71 100644 --- a/tests/templates/testfiles/test_dir_1/test_dir/subdir3/template_5/template_file.conf +++ b/tests/templates/testfiles/template_dir_1/test_dir/subdir3/template_5/template_file.conf @@ -1,4 +1,4 @@ -{% calculate name = 'important.conf', merge = 'kde', autoupdate -%} +{% calculate name = 'important.conf', merge = 'xfce-base/xfce4-panel', autoupdate -%} relay_domains = {{ merge.calculate_domains }} #Для создания базы используется postmap diff --git a/tests/templates/testfiles/template_dir_2/test_dir/.calculate_directory b/tests/templates/testfiles/template_dir_2/test_dir/.calculate_directory new file mode 100644 index 0000000..9911e16 --- /dev/null +++ b/tests/templates/testfiles/template_dir_2/test_dir/.calculate_directory @@ -0,0 +1 @@ +{% calculate append = 'skip', action = 'install', chown = 'divanov:guest' %} diff --git a/tests/templates/testfiles/template_dir_2/test_dir/templates/.calculate_directory b/tests/templates/testfiles/template_dir_2/test_dir/templates/.calculate_directory new file mode 100644 index 0000000..6c7b9af --- /dev/null +++ b/tests/templates/testfiles/template_dir_2/test_dir/templates/.calculate_directory @@ -0,0 +1 @@ +{% calculate append = 'join', name = 'test_root', path = test.test_root %} diff --git a/tests/templates/testfiles/template_dir_2/test_dir/templates/template_1/.calculate_directory b/tests/templates/testfiles/template_dir_2/test_dir/templates/template_1/.calculate_directory new file mode 100644 index 0000000..3c63e84 --- /dev/null +++ b/tests/templates/testfiles/template_dir_2/test_dir/templates/template_1/.calculate_directory @@ -0,0 +1,2 @@ +{% calculate name = 'configuration_1', path = '/etc', +package = 'xfce-extra/xfce4-clipman-plugin' %} diff --git a/tests/templates/testfiles/test_dir_2/test_dir/template_1/template_1.conf b/tests/templates/testfiles/template_dir_2/test_dir/templates/template_1/template_1.conf similarity index 77% rename from tests/templates/testfiles/test_dir_2/test_dir/template_1/template_1.conf rename to tests/templates/testfiles/template_dir_2/test_dir/templates/template_1/template_1.conf index d849d27..65f6edc 100644 --- a/tests/templates/testfiles/test_dir_2/test_dir/template_1/template_1.conf +++ b/tests/templates/testfiles/template_dir_2/test_dir/templates/template_1/template_1.conf @@ -1,5 +1,5 @@ {% calculate name = 'template', format = 'bind', append = 'before' -%} -{% calculate merge = 'package_2' -%} +{% calculate merge = 'xfce-base/xfce4-panel' -%} acl "trusted" { {{ merge.ip_value }}; 10.0.0.0/8; diff --git a/tests/templates/testfiles/template_dir_2/test_dir/templates/template_2/.calculate_directory b/tests/templates/testfiles/template_dir_2/test_dir/templates/template_2/.calculate_directory new file mode 100644 index 0000000..b2b541f --- /dev/null +++ b/tests/templates/testfiles/template_dir_2/test_dir/templates/template_2/.calculate_directory @@ -0,0 +1,2 @@ +{% calculate name = 'config.d', path = '/etc/dir', +package = 'xfce-extra/xfce4-screenshooter' %} diff --git a/tests/templates/testfiles/test_dir_2/test_dir/template_2/template_2.conf b/tests/templates/testfiles/template_dir_2/test_dir/templates/template_2/template_2.conf similarity index 68% rename from tests/templates/testfiles/test_dir_2/test_dir/template_2/template_2.conf rename to tests/templates/testfiles/template_dir_2/test_dir/templates/template_2/template_2.conf index 8451433..788042c 100644 --- a/tests/templates/testfiles/test_dir_2/test_dir/template_2/template_2.conf +++ b/tests/templates/testfiles/template_dir_2/test_dir/templates/template_2/template_2.conf @@ -1,4 +1,5 @@ -{% calculate name = 'wow_file.conf', force, format = 'samba', merge = 'kde' -%} +{% calculate name = 'wow_file.conf', force, format = 'samba', +merge = 'media-sound/alsa-utils, xfce-extra/xfce4-clipman-plugin' -%} [global] server role = standalone server hosts allow = 192.168.1. 192.168.2. 127. diff --git a/tests/templates/testfiles/test_dir_1/file.test b/tests/templates/testfiles/test_dir_1/file.test new file mode 100644 index 0000000..e69de29 diff --git a/tests/templates/testfiles/test_dir_1/test_dir/.calculate_directory b/tests/templates/testfiles/test_dir_1/test_dir/.calculate_directory deleted file mode 100644 index 5280952..0000000 --- a/tests/templates/testfiles/test_dir_1/test_dir/.calculate_directory +++ /dev/null @@ -1 +0,0 @@ -{% calculate append="skip" %} diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir2/.calculate_directory b/tests/templates/testfiles/test_dir_1/test_dir/subdir2/.calculate_directory deleted file mode 100644 index 23a941f..0000000 --- a/tests/templates/testfiles/test_dir_1/test_dir/subdir2/.calculate_directory +++ /dev/null @@ -1,4 +0,0 @@ -{% calculate name = 'important_dir', path = '/etc/folder/in_folder', action = 'install' -%} -{% calculate install.version > 2.0 %} -{% calculate package = 'kde' -%} -{% calculate merge.version < 1.2 -%} diff --git a/tests/templates/testfiles/test_dir_1/test_dir/subdir3/.calculate_directory b/tests/templates/testfiles/test_dir_1/test_dir/subdir3/.calculate_directory deleted file mode 100644 index 5ea524d..0000000 --- a/tests/templates/testfiles/test_dir_1/test_dir/subdir3/.calculate_directory +++ /dev/null @@ -1,2 +0,0 @@ -{% calculate name = 'directory', path = '/etc/important_dir', package = 'any' %} -{% calculate action = 'install' %} diff --git a/tests/templates/testfiles/test_dir_2/test_dir/.calculate_directory b/tests/templates/testfiles/test_dir_2/test_dir/.calculate_directory deleted file mode 100644 index 7ae6970..0000000 --- a/tests/templates/testfiles/test_dir_2/test_dir/.calculate_directory +++ /dev/null @@ -1 +0,0 @@ -{% calculate append = 'skip', action = 'install' %} diff --git a/tests/templates/testfiles/test_dir_2/test_dir/template_1/.calculate_directory b/tests/templates/testfiles/test_dir_2/test_dir/template_1/.calculate_directory deleted file mode 100644 index 4530c35..0000000 --- a/tests/templates/testfiles/test_dir_2/test_dir/template_1/.calculate_directory +++ /dev/null @@ -1 +0,0 @@ -{% calculate name = 'configuration_1', path = '/etc', package = 'package_1' %} diff --git a/tests/templates/testfiles/test_dir_2/test_dir/template_2/.calculate_directory b/tests/templates/testfiles/test_dir_2/test_dir/template_2/.calculate_directory deleted file mode 100644 index 0726732..0000000 --- a/tests/templates/testfiles/test_dir_2/test_dir/template_2/.calculate_directory +++ /dev/null @@ -1 +0,0 @@ -{% calculate name = 'config.d', path = '/etc/dir', package = 'package_2' %} diff --git a/tests/utils/test_files.py b/tests/utils/test_files.py index a4fe478..2b5a2e9 100644 --- a/tests/utils/test_files.py +++ b/tests/utils/test_files.py @@ -3,7 +3,7 @@ from calculate.utils.files import Process from subprocess import run, PIPE -@pytest.mark.files +@pytest.mark.files_utils class TestUtils(): def test_if_single_correct_command_executed_using_Process_object__it_successfully_executes(self): try: diff --git a/tests/utils/test_package.py b/tests/utils/test_package.py new file mode 100644 index 0000000..33aa74e --- /dev/null +++ b/tests/utils/test_package.py @@ -0,0 +1,183 @@ +import pytest +import os +from calculate.utils.package import Package, PackageAtom, PackageAtomError +from pprint import pprint +from collections import OrderedDict +import re + + +BASE_DIRECTORY = os.path.join(os.getcwd(), 'tests/utils/testfiles/') + + +@pytest.mark.package_utils +class TestContents: + def test_if_PackageContents_object_initialized_by_existing_package__it_contains_dictionary_of_items_from_contents_file(self): + result = OrderedDict({ + '/usr': + OrderedDict({'type': 'dir'}), + '/usr/bin': + OrderedDict({'type': 'dir'}), + '/usr/bin/rview': + OrderedDict({'type': 'sym', + 'target': 'vim', + 'mtime': '1573538053'}), + '/usr/bin/rvim': + OrderedDict({'type': 'sym', + 'target': 'vim', + 'mtime': '1573538053'}), + '/usr/bin/vim': + OrderedDict({'type': 'obj', + 'md5': '30acc0f256e11c1ecdb1bd80b688d238', + 'mtime': '1573538056'}), + '/usr/bin/vimdiff': + OrderedDict({'type': 'sym', + 'target': 'vim', + 'mtime': '1573538053'})}) + contents_object = Package('category_1/package_1', + chroot_path=BASE_DIRECTORY) + assert contents_object.contents_dictionary == result + + def test_if_PackageContents_object_contains_contents_dictionary__it_renders_CONTENTS_file_correctly(self): + contents_object = Package('category_1/package_1', + chroot_path=BASE_DIRECTORY) + result = '''dir /usr +dir /usr/bin +sym /usr/bin/rview -> vim 1573538053 +sym /usr/bin/rvim -> vim 1573538053 +obj /usr/bin/vim 30acc0f256e11c1ecdb1bd80b688d238 1573538056 +sym /usr/bin/vimdiff -> vim 1573538053 +''' + assert contents_object.render_contents_file() == result + + def test_if_new_directory_is_added_in_contents_file_using_add_dir_method__the_PackageContents_object_renders_the_contents_file_with_new_dir(self): + contents_object = Package('category_1/package_1', + chroot_path=BASE_DIRECTORY) + result = '''dir /usr +dir /usr/bin +sym /usr/bin/rview -> vim 1573538053 +sym /usr/bin/rvim -> vim 1573538053 +obj /usr/bin/vim 30acc0f256e11c1ecdb1bd80b688d238 1573538056 +sym /usr/bin/vimdiff -> vim 1573538053 +dir /etc +dir /etc/test_dir_1 +''' + contents_object.add_dir('/etc/test_dir_1') + assert contents_object.render_contents_file() == result + + def test_if_new_object_is_added_in_contents_file_using_add_obj_method__the_PackageContents_object_renders_the_contents_file_with_new_obj(self): + contents_object = Package('category_1/package_1', + chroot_path=BASE_DIRECTORY) + result = '''dir /usr +dir /usr/bin +sym /usr/bin/rview -> vim 1573538053 +sym /usr/bin/rvim -> vim 1573538053 +obj /usr/bin/vim 30acc0f256e11c1ecdb1bd80b688d238 1573538056 +sym /usr/bin/vimdiff -> vim 1573538053 +dir /etc +dir /etc/test_dir_2 +obj /etc/test_dir_2/file_2.cfg a371f4d456d471ac0ed0e8befff1cb6d 1586531028 +''' + contents_object.add_obj('/etc/test_dir_2/file_2.cfg') + print('RESULT:') + print(contents_object.render_contents_file()) + assert contents_object.render_contents_file() == result + + def test_if_new_link_is_added_in_contents_file_using_add_sym_method__the_PackageContents_object_renders_the_contents_file_with_new_sym(self): + contents_object = Package('category_1/package_1', + chroot_path=BASE_DIRECTORY) + result = '''dir /usr +dir /usr/bin +sym /usr/bin/rview -> vim 1573538053 +sym /usr/bin/rvim -> vim 1573538053 +obj /usr/bin/vim 30acc0f256e11c1ecdb1bd80b688d238 1573538056 +sym /usr/bin/vimdiff -> vim 1573538053 +dir /etc +dir /etc/test_dir_2 +sym /etc/test_dir_2/symlink -> file_2.cfg 1587117567 +''' + contents_object.add_sym('/etc/test_dir_2/symlink') + print('RESULT:') + print(contents_object.render_contents_file()) + assert contents_object.render_contents_file() == result + + def test_if_the_PackageAtom_object_parsed_a_correct_package_atom_name_but_without_a_slot_and_use_flags__the_PackageAtom_object_contains_name_and_category_values(self): + package_atom = PackageAtom() + package_atom.parse_package_parameter('dev-lang/python-3.6') + parsing_result = { + 'category': 'dev-lang', + 'contents': '/var/db/pkg/dev-lang/python-3.6.9/CONTENTS', + 'name': 'python-3.6.9' + } + assert package_atom._atom_dictionary == parsing_result + + def test_if_the_PackageAtom_object_parsed_a_correct_package_atom_name_with_a_slot_value__the_PackageAtom_object_contains_name_category_values_and_slot_value(self): + package_atom = PackageAtom() + package_atom.parse_package_parameter('dev-lang/python-3.6:3.6/3.6m') + parsing_result = { + 'category': 'dev-lang', + 'contents': '/var/db/pkg/dev-lang/python-3.6.9/CONTENTS', + 'name': 'python-3.6.9', + 'slot': '3.6/3.6m' + } + assert package_atom._atom_dictionary == parsing_result + + def test_if_the_PackageAtom_object_parsed_a_correct_package_atom_name_with_an_empty_slot_value__the_PackageAtom_object_contains_name_category_values_and_slot_value(self): + package_atom = PackageAtom() + package_atom.parse_package_parameter('dev-lang/python-3.6:') + parsing_result = { + 'category': 'dev-lang', + 'contents': '/var/db/pkg/dev-lang/python-3.6.9/CONTENTS', + 'name': 'python-3.6.9' + } + assert package_atom._atom_dictionary == parsing_result + + def test_if_the_PackageAtom_object_parsed_a_correct_package_atom_name_with_a_uses_value__the_PackageAtom_object_contains_name_category_values_and_slot_value(self): + package_atom = PackageAtom() + package_atom.parse_package_parameter('dev-lang/python-3.6 abi_x86_64 ssl') + parsing_result = { + 'category': 'dev-lang', + 'contents': '/var/db/pkg/dev-lang/python-3.6.9/CONTENTS', + 'name': 'python-3.6.9', + 'uses': ['abi_x86_64', 'ssl'] + } + assert package_atom._atom_dictionary == parsing_result + + def test_if_the_PackageAtom_object_parsed_a_correct_package_atom_name_and_add_slot_and_add_use_flags_is_set__the_PackageAtom_object_contains_name_category_slot_and_uses_values(self): + package_atom = PackageAtom() + package_atom.parse_package_parameter('dev-lang/python-3.6', + add_slot=True, + add_uses=True) + parsing_result = { + 'category': 'dev-lang', + 'contents': '/var/db/pkg/dev-lang/python-3.6.9/CONTENTS', + 'name': 'python-3.6.9' + } + assert parsing_result + + def test_if_the_PackageAtom_object_tried_to_parse_an_incorrect_package_atom_name__the_PackageAtom_object_throws_the_PackageAtomError_exception(self): + package_atom = PackageAtom() + with pytest.raises(PackageAtomError): + package_atom.parse_package_parameter( + 'dev-lang/something_i_can_never_have:3.6/3.6m') + + def test_if_the_PackageAtom_object_tried_to_parse_an_correct_package_atom_name_that_matches_multiple_packages__the_PackageAtom_object_throws_the_PackageAtomError_exception(self): + package_atom = PackageAtom() + with pytest.raises(PackageAtomError): + package_atom.parse_package_parameter( + 'dev-lang/python') + + def test_if_the_get_file_package_method_of_the_PackageAtom_object_is_called_with_a_name_of_a_file_that_belongs_to_any_package__the_PackageAtom_object_contains_dictionary_with_an_owner_package(self): + package_atom = PackageAtom() + package_atom_regex = re.compile(r'dev-lang/python-3.6.\d+') + try: + file_package = package_atom.get_file_package('/usr/bin/python3.6') + assert package_atom_regex.search(file_package) + except Exception as error: + pytest.fail('Unexpected exception: {0}'. + format(str(error))) + + def test_if_the_get_file_package_method_of_the_PackageAtom_object_is_called_with_a_name_of_a_file_that_does_not_belong_to_any_package__the_PackageAtom_object_throws_the_PackageAtomError_exception(self): + package_atom = PackageAtom() + with pytest.raises(PackageAtomError): + file_package = package_atom.get_file_package('/etc/shadow') + print('package = {}'.format(file_package)) diff --git a/tests/utils/testfiles/etc/test_dir_1/file_1.cfg b/tests/utils/testfiles/etc/test_dir_1/file_1.cfg new file mode 100644 index 0000000..deba01f --- /dev/null +++ b/tests/utils/testfiles/etc/test_dir_1/file_1.cfg @@ -0,0 +1 @@ +something diff --git a/tests/utils/testfiles/etc/test_dir_2/file_2.cfg b/tests/utils/testfiles/etc/test_dir_2/file_2.cfg new file mode 100644 index 0000000..91d50f2 --- /dev/null +++ b/tests/utils/testfiles/etc/test_dir_2/file_2.cfg @@ -0,0 +1 @@ +very important information... diff --git a/tests/utils/testfiles/etc/test_dir_2/symlink b/tests/utils/testfiles/etc/test_dir_2/symlink new file mode 120000 index 0000000..abae0d1 --- /dev/null +++ b/tests/utils/testfiles/etc/test_dir_2/symlink @@ -0,0 +1 @@ +file_2.cfg \ No newline at end of file diff --git a/tests/utils/testfiles/var/db/pkg/category_1/package_1/CONTENTS b/tests/utils/testfiles/var/db/pkg/category_1/package_1/CONTENTS new file mode 100644 index 0000000..920475f --- /dev/null +++ b/tests/utils/testfiles/var/db/pkg/category_1/package_1/CONTENTS @@ -0,0 +1,6 @@ +dir /usr +dir /usr/bin +sym /usr/bin/rview -> vim 1573538053 +sym /usr/bin/rvim -> vim 1573538053 +obj /usr/bin/vim 30acc0f256e11c1ecdb1bd80b688d238 1573538056 +sym /usr/bin/vimdiff -> vim 1573538053