diff --git a/calculate/templates/format/base_format.py b/calculate/templates/format/base_format.py index 442aa22..4fe7ca2 100644 --- a/calculate/templates/format/base_format.py +++ b/calculate/templates/format/base_format.py @@ -46,9 +46,9 @@ class BaseFormat(): if self._fatal_error_flag: # Действия если файл невозможно разобрать. - print('Can not parse file.') self._document_dictionary = OrderedDict() - return + raise FormatError('line {} is not correct.'. + format(self._line_timer)) if self._is_match(): if self._is_ready_to_update(): @@ -56,10 +56,9 @@ class BaseFormat(): break else: # Действия если не удалось разобрать строку. - print('Line', self._line_timer, - 'is not correct. Can not parse file.') self._document_dictionary = OrderedDict() - return + raise FormatError('line {} is not correct.'. + format(self._line_timer)) self._line_timer += 1 @@ -288,7 +287,8 @@ class BaseFormat(): **to_add_dictionary) return full_diff, unchanged_set - def get_document_text(self): + @property + def document_text(self): file_loader = PackageLoader('calculate.templates.format', self.TEMPLATES_DIRECTORY) formats_environment = Environment(loader=file_loader, diff --git a/calculate/templates/format/contents_format.py b/calculate/templates/format/contents_format.py index 2bc9976..73c59b2 100644 --- a/calculate/templates/format/contents_format.py +++ b/calculate/templates/format/contents_format.py @@ -124,7 +124,8 @@ class ContentsFormat(BaseFormat): except ParseException: return - def get_document_text(self): + @property + def document_text(self): file_loader = PackageLoader('calculate.templates.format', self.TEMPLATES_DIRECTORY) formats_environment = Environment(loader=file_loader, diff --git a/calculate/templates/format/json_format.py b/calculate/templates/format/json_format.py index cf93fe4..2b6fbe3 100644 --- a/calculate/templates/format/json_format.py +++ b/calculate/templates/format/json_format.py @@ -25,6 +25,7 @@ class JSONFormat(BaseFormat): self._document_dictionary = json.loads(json_file_text, object_pairs_hook=OrderedDict) + @property def get_document_text(self): json_file_text = json.dumps(self._document_dictionary, indent=4) return json_file_text diff --git a/calculate/templates/format/proftpd_format.py b/calculate/templates/format/proftpd_format.py index c8f999b..249600b 100644 --- a/calculate/templates/format/proftpd_format.py +++ b/calculate/templates/format/proftpd_format.py @@ -407,7 +407,8 @@ class ProFTPDFormat(BaseFormat): except ParseException: return - def get_document_text(self): + @property + def document_text(self): file_loader = PackageLoader('calculate.templates.format', self.TEMPLATES_DIRECTORY) formats_environment = Environment(loader=file_loader) diff --git a/calculate/templates/format/xml_gconf_format.py b/calculate/templates/format/xml_gconf_format.py index 73e8967..9fc81d2 100644 --- a/calculate/templates/format/xml_gconf_format.py +++ b/calculate/templates/format/xml_gconf_format.py @@ -164,7 +164,8 @@ class XMLGConfFormat(BaseFormat): element_name = ('', Element.tag) return OrderedDict({element_name: 'Unknown element'}) - def get_document_text(self): + @property + def document_text(self): gconf_header = next(iter(self._document_dictionary)) root = Element('gconf') diff --git a/calculate/templates/format/xml_xfce_format.py b/calculate/templates/format/xml_xfce_format.py index 627da49..f066009 100644 --- a/calculate/templates/format/xml_xfce_format.py +++ b/calculate/templates/format/xml_xfce_format.py @@ -112,7 +112,8 @@ class XMLXfceFormat(BaseFormat): element_name = ('', xml_element.tag) return OrderedDict({element_name: 'Unknown element'}) - def get_document_text(self): + @property + def document_text(self): channel = next(iter(self._document_dictionary.keys())) channel_head = OrderedDict( {key: value for key, value in diff --git a/calculate/templates/template_engine.py b/calculate/templates/template_engine.py index 73d5a7b..7bbfa5a 100644 --- a/calculate/templates/template_engine.py +++ b/calculate/templates/template_engine.py @@ -19,7 +19,7 @@ from ..utils.files import join_paths # Типы шаблона: директория или файл. -DIR, FILE = range(2) +DIR, FILE, LINK = range(3) class IncorrectParameter(Exception): @@ -81,9 +81,15 @@ class ParametersProcessor: inheritable_parameters = {'chmod', 'chown', 'autoupdate', 'env', 'package', 'action'} + file_default_parameters = {'chmod': 0o644, + 'chown': {'uid': 0, 'gid': 0}} + + directory_default_parameters = {'chmod': 0o755, + 'chown': {'uid': 0, 'gid': 0}} + available_appends = set() - available_formats = set() + available_formats = dict() format_is_inspected = False @@ -248,6 +254,13 @@ class ParametersProcessor: raise IncorrectParameter( "'restart' parameter value is not correct") + def check_format_parameter(self, parameter_value): + if parameter_value in self.available_formats: + return parameter_value + else: + raise IncorrectParameter( + "'format' parameter value is not available") + def check_stop_parameter(self, parameter_value): if parameter_value and isinstance(parameter_value, str): return parameter_value @@ -445,8 +458,10 @@ class ParametersProcessor: предоставляемых ими параметров.''' if cls.format_is_inspected: return + parameters_set = set() - format_set = set() + available_formats = dict() + format_directory_path = os.path.join(os.path.dirname(__file__), 'format') @@ -466,14 +481,21 @@ class ParametersProcessor: 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) + format_name = getattr(format_class, + 'FORMAT', False) + if not format_name: + continue + + available_formats.update( + {format_name: format_class}) + format_parameters = getattr(format_class, + 'FORMAT_PARAMETERS', + set()) + parameters_set.update(format_parameters) except Exception: continue - cls.available_formats = format_set + cls.available_formats = available_formats cls.available_parameters.update(parameters_set) cls.formats_inspected = True @@ -860,7 +882,7 @@ class CalculateExtension(Extension): class TemplateEngine: - def __init__(self, directory_path='/', + def __init__(self, directory_path=None, datavars_module=Variables(), appends_set=set(), chroot_path='/'): @@ -870,7 +892,6 @@ class TemplateEngine: ParametersProcessor.available_parameters CalculateExtension._datavars = datavars_module - self.available_formats = ParametersProcessor.available_formats self.available_appends = appends_set ParametersProcessor.available_appends = appends_set @@ -885,7 +906,11 @@ class TemplateEngine: CalculateExtension.parameters_processor = self.parameters_processor - self.environment = Environment(loader=FileSystemLoader(directory_path)) + if directory_path is not None: + self.environment = Environment( + loader=FileSystemLoader(directory_path)) + else: + self.environment = Environment() self.calculate_extension = CalculateExtension( self.environment, diff --git a/calculate/templates/template_processor.py b/calculate/templates/template_processor.py index bd41bbc..2b90920 100644 --- a/calculate/templates/template_processor.py +++ b/calculate/templates/template_processor.py @@ -31,10 +31,18 @@ class TemplateAction: def __init__(self, output_module=IOModule(), chroot_path='/', mounts=None): self.output = output_module + self.mounts = mounts + self.chroot_path = chroot_path + self.exec_list = OrderedDict() + self.directory_default_parameters =\ + ParametersProcessor.directory_default_parameters + self.file_default_parameters =\ + ParametersProcessor.file_default_parameters + self.DIRECTORY_APPENDS = { 'remove': self._append_remove_directory, 'clear': self._append_clear_directory, diff --git a/template_action_draft.py b/template_action_draft.py index f6d2ec0..9438659 100644 --- a/template_action_draft.py +++ b/template_action_draft.py @@ -1,26 +1,245 @@ from calculate.templates.template_engine import TemplateEngine, Variables,\ - ConditionFailed, DIR, FILE + FILE, DIR, LINK,\ + ParametersProcessor from calculate.templates.template_processor import TemplateAction +import shutil +import os -template_text = '''{% calculate append = 'join', path = '/etc/file.conf' %} -{% calculate package = "dev-lang/python" %} -{% calculate name = 'filename', format = 'samba' %} +template_text = '''{% calculate append = 'join' -%} +{% calculate package = 'dev-lang/python', format = 'samba' -%} [section one] -parameter_1 = value_1 -!parameter_2 = value_2 +parameter_1 = {{ vars_1.value_1 }} +!parameter_2 +''' + +backup_template_text = '''{% calculate append = 'join' -%} +[section one] +parameter_1 = value +parameter_2 = value_2 ''' APPENDS_SET = TemplateAction().available_appends -template_engine = TemplateEngine(appends_set=APPENDS_SET) -template_engine.process_template_from_string(template_text, FILE) -template_parameters = template_engine.parameters -target_path = '/etc/dir/file.conf' +vars_1 = Variables({'value_1': 'value_1'}) +DATAVARS_MODULE = Variables({'vars_1': vars_1}) + +CHROOT_PATH = os.path.join(os.getcwd(), 'tests/templates/testfiles/test_root') + +template_engine = TemplateEngine(datavars_module=DATAVARS_MODULE, + appends_set=APPENDS_SET, + chroot_path=CHROOT_PATH) + +target_path = os.path.join(CHROOT_PATH, + 'etc/dir/file.conf') + + +class TemplateActionError(Exception): + pass + + +class TargetFile: + type_checks = {DIR: os.path.isdir, + FILE: os.path.isfile, + LINK: os.path.islink} + + def __init__(self, target_file_path, template_type, package=None): + self._file_path = target_file_path + self._package = package + + if not os.path.exists(target_file_path): + pass + + def check_conflicts(self, parameters): + pass + + +class TemplateActionDraft: + def __init__(self, datavars_module=Variables(), chroot_path='/'): + self.datavars_module = datavars_module + + self.chroot_path = chroot_path + + self.directory_default_parameters =\ + ParametersProcessor.directory_default_parameters + + self.file_default_parameters =\ + ParametersProcessor.file_default_parameters + + self.directory_appends = {'join': self._append_join_directory, + 'remove': self._append_remove_directory, + 'clear': self._append_clear_directory} + + self.file_appends = {'join': self._append_join_file} + + self.formats_classes = ParametersProcessor.available_formats + + def use_directory_template(self, target_path, parameters): + print('Template parameters:') + parameters.print_parameters_for_debug() + + self.template_parameters = parameters + self.directory_appends[self.template_parameters.append](target_path) + + def use_file_template(self, target_path, parameters, template_text=''): + print('Template parameters:') + parameters.print_parameters_for_debug() + + self.template_parameters = parameters + self.template_text = template_text + self.file_appends[self.template_parameters.append](target_path) -def use_template(target_path, parameters, chroot_path): - print('Template parameters:') - parameters.print_parameters_for_debug() + def _append_join_directory(self, target_path): + print("append = 'join'") + 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 + + directories_to_create = [target_path] + directory_path = os.path.dirname(target_path) + + while not os.access(directory_path, os.F_OK) and directory_path: + directories_to_create.append(directory_path) + directory_path = os.path.dirname(directory_path) + + try: + current_mod, current_uid, current_gid = self.get_file_info( + directory_path, + 'all') + current_owner = {'uid': current_uid, 'gid': current_gid} + except OSError: + raise TemplateActionError('No access to the directory: {}'.format( + directory_path)) + + directories_to_create.reverse() + + for create_path in directories_to_create: + try: + os.mkdir(create_path) + + if (self.template_parameters.chmod and + self.template_parameters.chmod != current_mod): + self.chmod_directory(create_path) + elif 'chmod' in self.directory_default_parameters: + self.chmod_directory( + create_path, + chmod_value=self.directory_default_parameters['chown']) + + if (self.template_parameters.chown and + self.template_parameters.chown != current_owner): + self.chown_directory + elif 'chown' in self.directory_default_parameters: + self.chown_directory( + create_path, + chown_value=self.directory_default_parameters['chmod']) + + except OSError as error: + raise TemplateActionError( + 'Failed to create directory: {}, reason: {}'. + format(create_path, str(error))) + + def _append_remove_directory(self, target_path): + print("append = 'remove'") + if os.path.isdir(target_path) or os.path.exists(target_path): + try: + if os.path.islink(target_path): + os.unlink(target_path) + else: + shutil.rmtree(target_path) + return True + except Exception as error: + self.output.set_error("Failed to delete the directory: {}". + format(target_path)) + self.output.set_error("Reason: {}". + format(str(error))) + return False + else: + self.output.set_error("Failed to delete the directory: {}". + format(target_path)) + self.output.set_error( + "Target file is not directory or not exists.". + format(target_path)) + return False + + def _append_clear_directory(self, target_path): + print("append = 'clear'") + if os.path.isdir(target_path): + if os.path.exists(target_path): + try: + if os.path.islink(target_path): + os.unlink(target_path) + else: + shutil.rmtree(target_path) + return + except Exception as error: + raise TemplateActionError( + ("Failed to delete the directory: {}," + "reason: {}").format(target_path, + str(error))) + else: + error_message = "target directory does not exist" + else: + error_message = "target file is not directory" + + raise TemplateActionError( + "Failed to delete the directory: {}, reason: {}.". + format(target_path, error_message)) + + def _append_skip_directory(self, target_path): + print("append = 'skip'") + return + + def _append_join_file(self, target_path): + print("append = 'join'") + if not self.template_parameters.format: + print('Format not defined.') + return + + format_class = self.formats_classes[self.template_parameters.format] + + if os.path.exists(target_path): + with open(target_path, 'r') as original_file: + original_file_text = original_file.read() + print('ORIGINAL:') + print(original_file_text) + else: + open(target_path, 'w').close() + original_file_text = '' + + original_object = format_class(original_file_text) + template_object = format_class(self.template_text) + + print('TEMPLATE:') + print(self.template_text) + + original_object.join_template(template_object) + + print('RESULT:') + print(original_object.document_text) + + def _append_clear_file(self, target_path): + print("append = 'clear'") + try: + with open(target_path, 'w') as file: + file.truncate(0) + except IOError: + raise TemplateActionError("Failed to clear the file: {}". + format(target_path)) + return False + + +template_engine.process_template_from_string(template_text, FILE) +template_parameters = template_engine.parameters +template_text = template_engine.template_text +template_action_obj = TemplateActionDraft(datavars_module=DATAVARS_MODULE, + chroot_path=CHROOT_PATH) -use_template(target_path, template_parameters) +result = template_action_obj.use_file_template(target_path, + template_parameters, + template_text=template_text) diff --git a/tests/templates/testfiles/test_root/etc/dir/file.conf b/tests/templates/testfiles/test_root/etc/dir/file.conf new file mode 100644 index 0000000..e289472 --- /dev/null +++ b/tests/templates/testfiles/test_root/etc/dir/file.conf @@ -0,0 +1,5 @@ +[section one] +parameter_1 = value + +[section two] +parameter_3 = value_3