Added support for 'run' and 'exec' parameters. Implementation of the files appends is almost done.

packages
Иванов Денис 4 years ago
parent 2b2af024ba
commit 8229f8c677

@ -10,7 +10,8 @@ class DiffFormat(BaseFormat):
FORMAT = 'diff' FORMAT = 'diff'
EXECUTABLE = True EXECUTABLE = True
def __init__(self, document_text: str): def __init__(self, document_text: str,
join_before=False):
self._patch_text = document_text self._patch_text = document_text
self._root_path = '' self._root_path = ''
self._last_level = 0 self._last_level = 0

@ -14,7 +14,7 @@ class PatchFormat(BaseFormat):
FORMAT_PARAMETERS = {'multiline', 'dotall', 'comment'} FORMAT_PARAMETERS = {'multiline', 'dotall', 'comment'}
def __init__(self, document_text: str, multiline=False, dotall=False, def __init__(self, document_text: str, multiline=False, dotall=False,
comment_symbol=''): comment_symbol='', join_before=False):
processing_methods = OrderedDict() processing_methods = OrderedDict()
super().__init__(processing_methods) super().__init__(processing_methods)

@ -19,7 +19,7 @@ class ProcmailFormat(BaseFormat):
def __init__(self, document_text: str, def __init__(self, document_text: str,
ignore_comments=False, ignore_comments=False,
join_before=True, join_before=False,
comment_symbol=''): comment_symbol=''):
processing_methods = [self._parse_comment_line, processing_methods = [self._parse_comment_line,
self._parse_parameter_line, self._parse_parameter_line,
@ -28,6 +28,7 @@ class ProcmailFormat(BaseFormat):
super().__init__(processing_methods) super().__init__(processing_methods)
self._ignore_comments = ignore_comments self._ignore_comments = ignore_comments
self._comments_processing = True self._comments_processing = True
self._join_before = join_before
self._last_comments_list = [] self._last_comments_list = []

@ -42,6 +42,7 @@ class ProFTPDFormat(BaseFormat):
self._ignore_comments = ignore_comments self._ignore_comments = ignore_comments
self._need_finish = True self._need_finish = True
self._comments_processing = True self._comments_processing = True
self._join_before = join_before
self._section_stack = [] self._section_stack = []
self._actions_stack = [] self._actions_stack = []

@ -21,7 +21,7 @@ class XMLGConfFormat(BaseFormat):
cls._initialize_parser() cls._initialize_parser()
return super().__new__(cls) return super().__new__(cls)
def __init__(self, document_text: str): def __init__(self, document_text: str, join_before=False):
processing_methods = OrderedDict({'gconf': self._gconf, processing_methods = OrderedDict({'gconf': self._gconf,
'entry': self._entry, 'entry': self._entry,
'dir': self._dir, 'dir': self._dir,
@ -34,6 +34,7 @@ class XMLGConfFormat(BaseFormat):
super().__init__(processing_methods) super().__init__(processing_methods)
self._initialize_parser() self._initialize_parser()
self._join_before = join_before
self._parse_xml_to_dictionary(document_text) self._parse_xml_to_dictionary(document_text)

@ -21,13 +21,15 @@ class XMLXfceFormat(BaseFormat):
cls._initialize_parser() cls._initialize_parser()
return super().__new__(cls) return super().__new__(cls)
def __init__(self, document_text: str, ignore_comments=False): def __init__(self, document_text: str, ignore_comments=False,
join_before=False):
processing_methods = OrderedDict({'channel': self._channel, processing_methods = OrderedDict({'channel': self._channel,
'property': self._property, 'property': self._property,
'value': self._value, 'value': self._value,
'unknown': self._unknown}) 'unknown': self._unknown})
super().__init__(processing_methods) super().__init__(processing_methods)
self._initialize_parser() self._initialize_parser()
self._join_before = join_before
if document_text == '': if document_text == '':
self._document_dictionary = OrderedDict() self._document_dictionary = OrderedDict()

@ -15,7 +15,8 @@ import os
from ..utils.package import PackageAtomParser, PackageAtomError, NOTEXIST,\ from ..utils.package import PackageAtomParser, PackageAtomError, NOTEXIST,\
Version Version
from ..utils.files import join_paths from ..utils.files import join_paths, check_directory_link, check_command,\
FilesError
# Типы шаблона: директория или файл. # Типы шаблона: директория или файл.
@ -134,7 +135,9 @@ class ParametersProcessor:
self.postparse_checkers_list = OrderedDict({ self.postparse_checkers_list = OrderedDict({
'append': self.check_postparse_append, 'append': self.check_postparse_append,
'source': self.check_postparse_source, 'source': self.check_postparse_source,
'autoupdate': self.check_postparse_autoupdate}) 'autoupdate': self.check_postparse_autoupdate,
'run': self.check_postparse_run,
'exec': self.check_postparse_exec})
# Если параметр является наследуемым только при некоторых условиях -- # Если параметр является наследуемым только при некоторых условиях --
# указываем здесь эти условия. # указываем здесь эти условия.
@ -279,28 +282,34 @@ class ParametersProcessor:
"'format' parameter value is not available") "'format' parameter value is not available")
def check_stop_parameter(self, parameter_value): def check_stop_parameter(self, parameter_value):
if parameter_value and isinstance(parameter_value, str): if not parameter_value and isinstance(parameter_value, bool):
return parameter_value raise IncorrectParameter("'stop' parameter value is empty")
else: return parameter_value
raise IncorrectParameter("'stop' parameter value is not correct")
def check_start_parameter(self, parameter_value): def check_start_parameter(self, parameter_value):
if parameter_value and isinstance(parameter_value, str): if not parameter_value and isinstance(parameter_value, bool):
return parameter_value raise IncorrectParameter("'start' parameter value is empty")
else: return parameter_value
raise IncorrectParameter("'start' parameter value is not correct")
def check_run_parameter(self, parameter_value): def check_run_parameter(self, parameter_value):
if parameter_value and isinstance(parameter_value, str): if not parameter_value and isinstance(parameter_value, bool):
return parameter_value raise IncorrectParameter("'run' parameter value is empty")
else: try:
raise IncorrectParameter("'run' parameter value is not correct") interpreter_path = check_command(parameter_value)
except FilesError as error:
raise IncorrectParameter("interpreter from 'run' parameter not"
" found")
return interpreter_path
def check_exec_parameter(self, parameter_value): def check_exec_parameter(self, parameter_value):
if parameter_value and isinstance(parameter_value, str): if not parameter_value and isinstance(parameter_value, bool):
return parameter_value raise IncorrectParameter("'exec' parameter value is empty")
else: try:
raise IncorrectParameter("'exec' parameter value is not correct") interpreter_path = check_command(parameter_value)
except FilesError as error:
raise IncorrectParameter("interpreter from 'exec' parameter not"
" found")
return interpreter_path
def check_chown_parameter(self, parameter_value): def check_chown_parameter(self, parameter_value):
if not parameter_value or isinstance(parameter_value, bool): if not parameter_value or isinstance(parameter_value, bool):
@ -331,6 +340,9 @@ class ParametersProcessor:
raise IncorrectParameter("'chmod' parameter value is not correct") raise IncorrectParameter("'chmod' parameter value is not correct")
def check_source_parameter(self, parameter_value): def check_source_parameter(self, parameter_value):
if not parameter_value or isinstance(parameter_value, bool):
raise IncorrectParameter("'source' parameter value is empty")
if self.chroot_path != '/': if self.chroot_path != '/':
real_path = join_paths(self.chroot_path, parameter_value) real_path = join_paths(self.chroot_path, parameter_value)
else: else:
@ -342,15 +354,18 @@ class ParametersProcessor:
source_file_type = DIR if os.path.isdir(real_path) else FILE source_file_type = DIR if os.path.isdir(real_path) else FILE
if not parameter_value or isinstance(parameter_value, bool): # Проверяем, совпадают ли типы шаблона и файла, указанного в source
raise IncorrectParameter("'source' parameter value is empty") if (self.template_type != source_file_type):
if (self._parameters_container.append == 'link' and
self.template_type != source_file_type):
raise IncorrectParameter( raise IncorrectParameter(
"the type of the 'source' file does not match" "the type of the 'source' file does not match"
" the type of the template file") " the type of the template file")
# Проверяем, не является ли файл из source зацикленной ссылкой.
if (source_file_type == DIR and os.path.islink(real_path)
and not check_directory_link(real_path)):
raise IncorrectParameter(
"the link from 'source' parameter is cycled")
return os.path.normpath(real_path) return os.path.normpath(real_path)
def check_env_parameter(self, parameter_value): def check_env_parameter(self, parameter_value):
@ -395,6 +410,32 @@ class ParametersProcessor:
raise IncorrectParameter("append = 'link' without source " raise IncorrectParameter("append = 'link' without source "
"parameter.") "parameter.")
if self._parameters_container.run:
raise IncorrectParameter("'append' parameter is not 'compatible' "
"with the 'run' parameter")
if self._parameters_container.exec:
raise IncorrectParameter("'append' parameter is not 'compatible' "
"with the 'exec' parameter")
def check_postparse_run(self, parameter_value):
if self._parameters_container.append:
raise IncorrectParameter("'run' parameter is not 'compatible' "
"with the 'append' parameter")
if self._parameters_container.exec:
raise IncorrectParameter("'run' parameter is not 'compatible' "
"with the 'exec' parameter")
def check_postparse_exec(self, parameter_value):
if self._parameters_container.append:
raise IncorrectParameter("'exec' parameter is not 'compatible' "
"with the 'append' parameter")
if self._parameters_container.run:
raise IncorrectParameter("'exec' parameter is not 'compatible' "
"with the 'run' parameter")
def check_postparse_source(self, parameter_value): def check_postparse_source(self, parameter_value):
# Если файл по пути source не существует, но присутствует параметр # Если файл по пути source не существует, но присутствует параметр
# mirror -- пропускаем шаблон для того, чтобы целевой файл мог быть # mirror -- пропускаем шаблон для того, чтобы целевой файл мог быть

@ -38,7 +38,7 @@ class KeyboardInputProcess():
class Process(): class Process():
'''Обертка для работы с процессами.''' '''Класс-обертка для работы с процессами.'''
STDOUT = STDOUT STDOUT = STDOUT
PIPE = PIPE PIPE = PIPE
@ -94,6 +94,7 @@ class Process():
return self.stdin_handler return self.stdin_handler
def _open_process(self): def _open_process(self):
'''Метод для открытия процесса.'''
try: try:
piped_stdin = self._stdin._get_stdout() piped_stdin = self._stdin._get_stdout()
self._process = Popen(self._command, self._process = Popen(self._command,
@ -128,6 +129,7 @@ class Process():
raise FilesError('Can not open process.') raise FilesError('Can not open process.')
def close(self): def close(self):
'''Метод для закрытия процесса.'''
if self._opened: if self._opened:
if self._process.stdin: if self._process.stdin:
self.stdin_handler.close() self.stdin_handler.close()
@ -135,6 +137,7 @@ class Process():
self._opened = False self._opened = False
def write(self, data): def write(self, data):
'''Метод для записи данных в stdin процесса.'''
if not self._opened: if not self._opened:
self._open_process() self._open_process()
self._is_read = False self._is_read = False
@ -149,6 +152,7 @@ class Process():
raise FilesError(str(error)) raise FilesError(str(error))
def read(self): def read(self):
'''Метод для чтения данных из stdout процесса.'''
if not self._opened and not self._writable: if not self._opened and not self._writable:
self._open_process() self._open_process()
@ -173,6 +177,8 @@ class Process():
raise raise
def read_error(self): def read_error(self):
'''Метод для чтения ошибок, появившихся при выполнении процесса, из его
stderr.'''
self.read() self.read()
if not self._error_cache: if not self._error_cache:
try: try:
@ -182,6 +188,7 @@ class Process():
return self._error_cache return self._error_cache
def kill(self): def kill(self):
'''Метод для удаления процесса, если он работает.'''
if self._opened: if self._opened:
self._process.kill() self._process.kill()
@ -198,22 +205,28 @@ class Process():
@property @property
def writable(self): def writable(self):
'''Метод для проверки возможности записи данных в во входной поток
процесса.'''
return self._writable return self._writable
@property @property
def readable(self): def readable(self):
'''Метод для проверки возможности чтения вывода процесса.'''
return self._readable return self._readable
@property @property
def readable_errors(self): def readable_errors(self):
'''Метод для проверки возможности чтения ошибок.'''
return self._readable_errors return self._readable_errors
def return_code(self): def return_code(self):
'''Метод возвращающий код возвращенный процессом.'''
self.read() self.read()
return self._process.returncode return self._process.returncode
@property @property
def shell_command(self): def shell_command(self):
'''Метод для получения эквивалентной консольной команды.'''
command = ' '.join(self._command) command = ' '.join(self._command)
previous_commands = self._stdin.shell_command previous_commands = self._stdin.shell_command
if previous_commands == '': if previous_commands == '':
@ -222,13 +235,17 @@ class Process():
return ' | '.join([previous_commands, command]) return ' | '.join([previous_commands, command])
def success(self): def success(self):
'''Метод для проверки успешности выполнения процесса.'''
return self.return_code() == 0 return self.return_code() == 0
def failed(self): def failed(self):
'''Метод для проверки неуспешности выполнения процесса.'''
return self.return_code() != 0 return self.return_code() != 0
class ProgramPathCache: class ProgramPathCache:
'''Класс, для поиска и кэширования путей к исполнительным файлам различных
команд.'''
def __init__(self): def __init__(self):
self._cache = {} self._cache = {}
@ -256,7 +273,8 @@ class ProgramPathCache:
get_program_path = ProgramPathCache() get_program_path = ProgramPathCache()
def check_utils(*utils): def check_command(*utils):
'''Функция для проверки наличия той или иной команды системе.'''
output = [] output = []
for util in utils: for util in utils:
util_path = get_program_path(util) util_path = get_program_path(util)
@ -271,6 +289,7 @@ def check_utils(*utils):
def join_paths(*paths): def join_paths(*paths):
'''Функция для объединения путей. Объединяет также абсолютные пути.'''
if len(paths) == 1: if len(paths) == 1:
return next(iter(paths)) return next(iter(paths))
@ -288,9 +307,12 @@ def join_paths(*paths):
def read_link(file_path): def read_link(file_path):
'''Функция для получения целевого пути символьной ссылки.'''
try: try:
if path.exists(file_path): if path.exists(file_path):
return os.readlink(file_path) return os.readlink(file_path)
else:
return None
except (OSError, IOError) as error: except (OSError, IOError) as error:
mod, lineno = get_traceback_caller(*sys.exc_info()) mod, lineno = get_traceback_caller(*sys.exc_info())
FilesError("link read error, {}({}:{})". FilesError("link read error, {}({}:{})".
@ -298,6 +320,7 @@ def read_link(file_path):
def read_file(file_path): def read_file(file_path):
'''Функция для чтения файлов, возвращает текст файла.'''
try: try:
if path.exists(file_path): if path.exists(file_path):
with open(file_path, 'r') as opened_file: with open(file_path, 'r') as opened_file:
@ -309,6 +332,8 @@ def read_file(file_path):
def write_file(file_path): def write_file(file_path):
'''Функция для открытия и записи файлов. Создает директории на пути к
целевому файлу если это необходимо. Возвращает файловый объект.'''
directory_path = path.dirname(file_path) directory_path = path.dirname(file_path)
if not path.exists(directory_path): if not path.exists(directory_path):
os.makedirs(directory_path) os.makedirs(directory_path)
@ -317,6 +342,7 @@ def write_file(file_path):
def read_file_lines(file_name, grab=False): def read_file_lines(file_name, grab=False):
'''Функция для чтения файлов построчно.'''
try: try:
if path.exists(file_name): if path.exists(file_name):
for file_line in open(file_name, 'r'): for file_line in open(file_name, 'r'):
@ -377,6 +403,47 @@ def make_directory(directory_path, force=False):
return False return False
def check_directory_link(link_path):
'''Метод для проверки наличия зацикливающихся ссылок и их корректности в
целом. В случае успешной проверки возвращает целевой путь ссылки.'''
link_target = read_link(link_path)
if link_target is None:
# Ссылка не существует.
return False
if not os.path.isdir(link_target):
# Ссылка не на директорию.
return False
linked_path = os.path.abspath(link_target)
# Добавляем / к концу пути, чтобы показать, что это путь к директории.
if linked_path[-1] != '/':
linked_path = linked_path + '/'
# Пути, которые нужно проверить.
to_check = [linked_path]
# Целевые пути из встреченных ссылок.
linked_paths = {linked_path}
while to_check:
current_directory = to_check.pop()
for entry in os.scandir(current_directory):
# Обходим только директории и ссылки на директории.
if not entry.is_dir():
continue
if entry.is_symlink():
linked_path = read_link(entry.path)
if linked_path in linked_paths:
return False
linked_paths.add(linked_path)
to_check.append(linked_path)
else:
to_check.append(entry.path)
return link_target
class RealFS(GenericFS): class RealFS(GenericFS):
def __init__(self, prefix='/'): def __init__(self, prefix='/'):
self.prefix = prefix self.prefix = prefix

@ -3,7 +3,8 @@ from calculate.templates.template_engine import TemplateEngine, Variables,\
from calculate.templates.template_processor import TemplateAction from calculate.templates.template_processor import TemplateAction
from calculate.utils.package import PackageAtomParser, Package, PackageNotFound from calculate.utils.package import PackageAtomParser, Package, PackageNotFound
from calculate.utils.files import write_file, read_link, read_file_lines,\ from calculate.utils.files import write_file, read_link, read_file_lines,\
FilesError, join_paths FilesError, join_paths,\
check_directory_link, Process
from calculate.utils.mount import Mounts from calculate.utils.mount import Mounts
from collections import OrderedDict from collections import OrderedDict
import hashlib import hashlib
@ -12,25 +13,32 @@ import glob
import shutil import shutil
import os import os
template_text = '''{% calculate append = 'link', source = '/etc/dir/dir_2' -%} template_text = '''{% calculate append = 'join' -%}
{% calculate package = 'test-category/test-package', format = 'samba' -%} {% calculate package = 'test-category/test-package', format = 'samba' -%}
[section one] [section one]
parameter_1 = {{ vars_1.value_1 }} parameter_1 = {{ vars_1.value_1 }}
!parameter_2 !parameter_2
''' '''
template_to_run = '''{% calculate run = "/usr/bin/python" -%}
with open('etc/dir/file.conf', 'r') as input_file:
print(input_file.read())
'''
backup_template_text = '''{% calculate append = 'join', format = 'samba', backup_template_text = '''{% calculate append = 'join', format = 'samba',
autoupdate, package = 'test-category/test-package' -%} package = 'test-category/test-package' -%}
[section one] [section one]
parameter_1 = value parameter_1 = value
parameter_2 = value_2 parameter_2 = value_2
[section two] [section two]
other_parameter = other_value other_parameter = other_value
[!section_name]
''' '''
APPENDS_SET = TemplateAction().available_appends APPENDS_SET = TemplateAction().available_appends
vars_1 = Variables({'value_1': 'value_1'}) vars_1 = Variables({'value_1': 'value_1', 'value_2': 'value_to_print',
'value_3': 5})
DATAVARS_MODULE = Variables({'vars_1': vars_1}) DATAVARS_MODULE = Variables({'vars_1': vars_1})
CHROOT_PATH = os.path.join(os.getcwd(), 'tests/templates/testfiles/test_root') CHROOT_PATH = os.path.join(os.getcwd(), 'tests/templates/testfiles/test_root')
@ -43,6 +51,7 @@ template_engine = TemplateEngine(datavars_module=DATAVARS_MODULE,
chroot_path=CHROOT_PATH) chroot_path=CHROOT_PATH)
target_path = os.path.join(CHROOT_PATH, 'etc/dir/file.conf') target_path = os.path.join(CHROOT_PATH, 'etc/dir/file.conf')
run_target_path = os.path.join(CHROOT_PATH, 'file_to_run.py')
class TemplateExecutorError(Exception): class TemplateExecutorError(Exception):
@ -196,13 +205,19 @@ class TemplateWrapper:
# Временный флаг для определения того, является ли шаблон userspace. # Временный флаг для определения того, является ли шаблон userspace.
self.is_userspace = False self.is_userspace = False
# Получаем класс соответствующего формата файла. if self.parameters.run or self.parameters.exec:
if self.parameters.format: # Если есть параметр run или exec, то кроме текста шаблона ничего
self.format_class = ParametersProcessor.\ # не нужно.
available_formats[self.parameters.format] return
else:
# Здесь будет детектор форматов. if self.parameters.append in {'join', 'before', 'after'}:
pass # Получаем класс соответствующего формата файла.
if self.parameters.format:
self.format_class = ParametersProcessor.\
available_formats[self.parameters.format]
else:
# Здесь будет детектор форматов.
pass
# Если по этому пути что-то есть -- проверяем конфликты. # Если по этому пути что-то есть -- проверяем конфликты.
if os.path.exists(target_file_path): if os.path.exists(target_file_path):
@ -243,27 +258,26 @@ class TemplateWrapper:
if self.parameters.force: if self.parameters.force:
self.remove_original = True self.remove_original = True
elif self.target_type == DIR: elif self.target_type == DIR:
raise TemplateTypeConflict( raise TemplateTypeConflict("the target is a directory while "
"The target is a directory while the" "the template has append = 'link'")
" template has append = 'link'.")
else: else:
raise TemplateTypeConflict( raise TemplateTypeConflict("the target is a file while the"
"The target is a file while the" " template has append = 'link'")
" template has append = 'link'.")
elif self.template_type == DIR: elif self.template_type == DIR:
if self.target_type == FILE: if self.target_type == FILE:
if self.parameters.force: if self.parameters.force:
self.remove_original = True self.remove_original = True
else: else:
raise TemplateTypeConflict("The target is a file while the" raise TemplateTypeConflict("the target is a file while the"
" template is a directory.") " template is a directory")
elif self.target_is_link and self.target_type == DIR: elif self.target_is_link:
if self.parameters.force: if self.parameters.force:
self.remove_original = True self.remove_original = True
else: else:
try: try:
self.target_path = read_link(self.target_path) self.target_path = check_directory_link(
self.target_path)
except FilesError as error: except FilesError as error:
raise TemplateExecutorError("files error: {}". raise TemplateExecutorError("files error: {}".
format(str(error))) format(str(error)))
@ -279,8 +293,8 @@ class TemplateWrapper:
raise TemplateExecutorError("files error: {}". raise TemplateExecutorError("files error: {}".
format(str(error))) format(str(error)))
elif self.target_type == DIR: elif self.target_type == DIR:
raise TemplateTypeConflict("The target file is a directory" raise TemplateTypeConflict("the target file is a directory"
" while the template is a file.") " while the template is a file")
def check_package_collision(self): def check_package_collision(self):
'''Проверка на предмет коллизии, то есть конфликта пакета шаблона и '''Проверка на предмет коллизии, то есть конфликта пакета шаблона и
@ -433,11 +447,9 @@ class TemplateWrapper:
def remove_from_contents(self): def remove_from_contents(self):
'''Метод для удаления целевого файла из CONTENTS.''' '''Метод для удаления целевого файла из CONTENTS.'''
print('let s remove')
if self.template_type == DIR: if self.template_type == DIR:
self.target_package.remove_dir(self.target_path) self.target_package.remove_dir(self.target_path)
elif self.template_type == FILE: elif self.template_type == FILE:
print('remove as file')
self.target_package.remove_obj(self.target_path) self.target_package.remove_obj(self.target_path)
def clear_dir_contents(self): def clear_dir_contents(self):
@ -483,8 +495,6 @@ class TemplateWrapper:
def save_changes(self): def save_changes(self):
'''Метод для сохранения изменений внесенных в CONTENTS.''' '''Метод для сохранения изменений внесенных в CONTENTS.'''
if self.target_package: if self.target_package:
print('saving CONTENTS: {}'.format(
self.target_package.contents_dictionary.keys()))
self.target_package.remove_empty_directories() self.target_package.remove_empty_directories()
self.target_package.write_contents_file() self.target_package.write_contents_file()
@ -492,11 +502,14 @@ class TemplateWrapper:
class TemplateExecutor: class TemplateExecutor:
def __init__(self, datavars_module=Variables(), chroot_path='/', def __init__(self, datavars_module=Variables(), chroot_path='/',
cl_config_archive='/var/lib/calculate/config-archive', cl_config_archive='/var/lib/calculate/config-archive',
cl_config_path='/var/lib/calculate/config'): cl_config_path='/var/lib/calculate/config',
exec_dir_path='/var/lib/calculate/.execute/'):
self.datavars_module = datavars_module self.datavars_module = datavars_module
self.chroot_path = chroot_path self.chroot_path = chroot_path
self.exec_files_directory = '/var/lib/calculate/.execute/'
self.directory_default_parameters =\ self.directory_default_parameters =\
ParametersProcessor.directory_default_parameters ParametersProcessor.directory_default_parameters
self.file_default_parameters =\ self.file_default_parameters =\
@ -509,7 +522,14 @@ class TemplateExecutor:
'link': self._append_link_directory, 'link': self._append_link_directory,
'replace': self._append_replace_directory} 'replace': self._append_replace_directory}
self.file_appends = {'join': self._append_join_file} self.file_appends = {'join': self._append_join_file,
'after': self._append_after_file,
'before': self._append_before_file,
'replace': self._append_replace_file,
'remove': self._append_remove_file,
'skip': self._append_skip_file,
'clear': self._append_clear_file,
'link': self._append_link_file}
self.formats_classes = ParametersProcessor.available_formats self.formats_classes = ParametersProcessor.available_formats
self.calculate_config_file = CalculateConfigFile( self.calculate_config_file = CalculateConfigFile(
@ -530,21 +550,24 @@ class TemplateExecutor:
def execute_template(self, target_path, parameters, template_type, def execute_template(self, target_path, parameters, template_type,
template_text=''): template_text=''):
'''Метод для запуска выполнения шаблонов.''' '''Метод для запуска выполнения шаблонов.'''
self.executor_output = {'target_path': None,
'stdout': None,
'stderr': None,
'exec_file': None}
try: try:
template_object = TemplateWrapper(target_path, parameters, template_object = TemplateWrapper(target_path, parameters,
template_type, template_type,
template_text=template_text) template_text=template_text)
except TemplateTypeConflict as error: except TemplateTypeConflict as error:
print('Error: {}'.format(str(error))) raise TemplateExecutorError("type conflict: {}".format(str(error)))
return
except TemplateCollisionError as error: except TemplateCollisionError as error:
print('Error: {}'.format(str(error))) raise TemplateExecutorError("collision: {}".format(str(error)))
return
# Удаляем оригинал, если это необходимо из-за наличия force или по # Удаляем оригинал, если это необходимо из-за наличия force или по
# другим причинам. # другим причинам.
if template_object.remove_original: if template_object.remove_original:
print('remove original')
if template_object.target_type == DIR: if template_object.target_type == DIR:
self._remove_directory(template_object.target_path) self._remove_directory(template_object.target_path)
else: else:
@ -557,25 +580,35 @@ class TemplateExecutor:
return return
template_object.target_type = None template_object.target_type = None
print('input path: {}'.format(template_object.input_path)) if template_object.parameters.run:
print('output path: {}'.format(template_object.output_path)) # Если есть параметр run -- запускаем текст шаблона.
self._run_template(template_object)
# (!) Добавить поддержку run, execute и т.д. elif template_object.parameters.exec:
if template_object.template_type == DIR: # Если есть параметр exec -- запускаем текст шаблона после
self.directory_appends[template_object.parameters.append]( # обработки всех шаблонов.
self._exec_template(template_object)
elif template_object.parameters.append:
print('Using append: {}'.format(template_object.parameters.append))
print('input path: {}'.format(template_object.input_path))
print('output path: {}'.format(template_object.output_path))
if template_object.template_type == DIR:
self.directory_appends[template_object.parameters.append](
template_object) template_object)
elif template_object.template_type == FILE: elif template_object.template_type == FILE:
self.file_appends[template_object.parameters.append]( self.file_appends[template_object.parameters.append](
template_object) template_object)
# Сохраняем изменения в CONTENTS внесенные согласно шаблону.
print('it is time to save changes')
template_object.save_changes()
# Возвращаем целевой путь, если он был изменен, или None если не был. # Сохраняем изменения в CONTENTS внесенные согласно шаблону.
if template_object.target_path_is_changed: template_object.save_changes()
return template_object.target_path
else: # Возвращаем целевой путь, если он был изменен, или
return # None если не был изменен.
if template_object.target_path_is_changed:
self.executor_output['target_path'] =\
template_object.target_path
return self.executor_output
def save_changes(self): def save_changes(self):
'''Метод для сохранения чего-нибудь после выполнения всех шаблонов.''' '''Метод для сохранения чего-нибудь после выполнения всех шаблонов.'''
@ -628,7 +661,8 @@ class TemplateExecutor:
self._clear_directory(template_object.target_path) self._clear_directory(template_object.target_path)
template_object.clear_dir_contents() template_object.clear_dir_contents()
def _append_join_file(self, template_object: TemplateWrapper): def _append_join_file(self, template_object: TemplateWrapper,
join_before=False, replace=False):
'''Метод описывающий действия при append = "join", если шаблон -- '''Метод описывающий действия при append = "join", если шаблон --
файл. Объединяет шаблон с целевым файлом.''' файл. Объединяет шаблон с целевым файлом.'''
input_path = template_object.input_path input_path = template_object.input_path
@ -644,14 +678,15 @@ class TemplateExecutor:
if template_object.protected and not template_object.is_userspace: if template_object.protected and not template_object.is_userspace:
output_paths.append(template_object.archive_path) output_paths.append(template_object.archive_path)
if template_object.target_type is not None: if template_object.target_type is not None and not replace:
with open(input_path, 'r') as input_file: with open(input_path, 'r') as input_file:
input_text = input_file.read() input_text = input_file.read()
else: else:
input_text = '' input_text = ''
parsed_input = template_format(input_text) parsed_input = template_format(input_text, join_before=join_before)
parsed_template = template_format(template_object.template_text) parsed_template = template_format(template_object.template_text,
join_before=join_before)
parsed_input.join_template(parsed_template) parsed_input.join_template(parsed_template)
@ -683,20 +718,19 @@ class TemplateExecutor:
# Убираем целевой файл из CL. # Убираем целевой файл из CL.
self.calculate_config_file.remove_file(template_object.target_path) self.calculate_config_file.remove_file(template_object.target_path)
print('is contents update is needed')
# Обновляем CONTENTS. # Обновляем CONTENTS.
if template_object.protected: if template_object.protected:
print('needed')
if template_object.parameters.unbound: if template_object.parameters.unbound:
print('remove')
template_object.remove_from_contents() template_object.remove_from_contents()
else: else:
print('add')
template_object.add_to_contents(file_md5=output_text_md5) template_object.add_to_contents(file_md5=output_text_md5)
else: else:
with open(input_path, 'r') as input_file: if template_object.target_type is not None and not replace:
input_text = input_file.read() with open(input_path, 'r') as input_file:
parsed_input = template_format(input_text) input_text = input_file.read()
parsed_input = template_format(input_text)
else:
input_text = ''
parsed_template = template_format(template_object.template_text) parsed_template = template_format(template_object.template_text)
parsed_input.join_template(parsed_template) parsed_input.join_template(parsed_template)
@ -732,6 +766,39 @@ class TemplateExecutor:
# Обновляем CONTENTS. # Обновляем CONTENTS.
template_object.add_to_contents(file_md5=output_text_md5) template_object.add_to_contents(file_md5=output_text_md5)
def _append_after_file(self, template_object: TemplateWrapper):
self._append_join_file(self, template_object, join_before=False)
def _append_before_file(self, template_object: TemplateWrapper):
self._append_join_file(self, template_object, join_before=True)
def _append_skip_file(self, template_object: TemplateWrapper):
pass
def _append_replace_file(self, template_object: TemplateWrapper):
self._append_join_file(template_object, replace=True)
def _append_remove_file(self, template_object: TemplateWrapper):
if template_object.target_type is not None:
self._remove_file(template_object.target_path)
template_object.remove_from_contents()
def _append_clear_file(self, template_object: TemplateWrapper):
if template_object.target_type is not None:
self._clear_file(template_object.target_path)
else:
open(template_object.target_path, 'w').close()
template_object.add_to_contents()
def _append_link_file(self, template_object: TemplateWrapper):
if template_object.target_type is not None:
self._link_file(template_object.target_path,
template_object.parameters.source)
template_object.add_to_contents()
def _create_directory(self, template_object: TemplateWrapper): def _create_directory(self, template_object: TemplateWrapper):
'''Метод для создания директории и, при необходимости, изменения '''Метод для создания директории и, при необходимости, изменения
владельца и доступа все директорий на пути к целевой.''' владельца и доступа все директорий на пути к целевой.'''
@ -920,6 +987,63 @@ class TemplateExecutor:
raise TemplateExecutorError('Can not chmod file: {0}, reason: {1}'. raise TemplateExecutorError('Can not chmod file: {0}, reason: {1}'.
format(target_path, str(error))) format(target_path, str(error)))
def _run_template(self, template_object: TemplateWrapper):
'''Метод для сохранения текста шаблонов, который должен быть исполнен
интерпретатором указанным в run прямо во время обработки шаблонов.'''
text_to_run = template_object.template_text
interpreter = template_object.parameters.run
try:
run_process = Process(interpreter, cwd=self.chroot_path)
run_process.write(text_to_run)
if run_process.readable:
stdout = run_process.read()
if stdout:
print("Run output:\n{}".format(stdout))
self.executor_output['stdout'] = stdout
if run_process.readable_errors:
stderr = run_process.read_error()
if stderr:
print("Run errors:\n{}".format(stderr))
self.executor_output['stderr'] = stderr
except FilesError as error:
raise TemplateExecutorError(("can not run template using the"
" interpreter '{}', reason: {}").
format(interpreter, str(error)))
def _exec_template(self, template_object: TemplateWrapper):
'''Метод для сохранения текста шаблонов, который должен быть исполнен
интерпретатором указанным в exec после выполнения всех прочих шаблонов.
'''
text_to_run = template_object.template_text
interpreter = template_object.parameters.exec
if (self.chroot_path != '/' and not
self.exec_files_directory.startswith(self.chroot_path)):
exec_files_directory = join_paths(self.chroot_path,
'/var/lib/calculate/.execute/')
exec_number = 0
if os.path.exists(exec_files_directory):
exec_files_list = os.listdir(exec_files_directory)
if exec_files_list:
exec_number = int(exec_files_list[-1][-4:])
exec_number = str(exec_number + 1)
if len(exec_number) < 4:
exec_number = '0' * (4 - len(exec_number)) + exec_number
exec_file_name = 'exec_{}'.format(exec_number)
exec_file_path = join_paths(exec_files_directory,
exec_file_name)
exec_file = write_file(exec_file_path)
exec_file.write(text_to_run)
exec_file.close()
self.executor_output['exec_file'] = {interpreter: exec_file_name}
def get_file_info(self, path, info='all'): def get_file_info(self, path, info='all'):
file_stat = os.stat(path) file_stat = os.stat(path)
if info == 'all': if info == 'all':
@ -1005,3 +1129,17 @@ template_executor_obj.execute_template(target_path,
template_parameters, template_parameters,
FILE, template_text=template_text) FILE, template_text=template_text)
template_executor_obj.save_changes() template_executor_obj.save_changes()
input()
template_engine.process_template_from_string(template_to_run, FILE)
template_parameters = template_engine.parameters
template_text = template_engine.template_text
template_executor_obj = TemplateExecutor(
datavars_module=DATAVARS_MODULE,
chroot_path=CHROOT_PATH,
cl_config_archive=CL_CONFIG_ARCHIVE_PATH,
cl_config_path=CL_CONFIG_PATH)
template_executor_obj.execute_template(target_path,
template_parameters,
FILE, template_text=template_text)
template_executor_obj.save_changes()

@ -0,0 +1,3 @@
# Source file
[section_name]
rare_parameter = eternal_value

@ -1,3 +1,3 @@
dir /etc dir /etc
dir /etc/dir dir /etc/dir
obj /etc/dir/file.conf 0b87fea7f5b65cac5012baa2bf647e72 1590588845 obj /etc/dir/file.conf 0b87fea7f5b65cac5012baa2bf647e72 1590678156

Loading…
Cancel
Save