Support of the handler is added.

handlers
Иванов Денис 4 years ago
parent 6561bb2c00
commit 8959bddcdc

@ -96,7 +96,7 @@ class ParametersProcessor:
'notify'} 'notify'}
inheritable_parameters = {'chmod', 'chown', 'autoupdate', 'env', inheritable_parameters = {'chmod', 'chown', 'autoupdate', 'env',
'package', 'action'} 'package', 'action', 'handler'}
# Параметры по умолчанию для файлов -- # Параметры по умолчанию для файлов --
# будут заполняться из __datavars__ # будут заполняться из __datavars__
@ -158,7 +158,8 @@ class ParametersProcessor:
'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, 'run': self.check_postparse_run,
'exec': self.check_postparse_exec}) 'exec': self.check_postparse_exec,
'handler': self.check_postparse_handler})
# Если параметр является наследуемым только при некоторых условиях -- # Если параметр является наследуемым только при некоторых условиях --
# указываем здесь эти условия. # указываем здесь эти условия.
@ -447,10 +448,10 @@ class ParametersProcessor:
f" {type(parameter_value)}.") f" {type(parameter_value)}.")
def check_handler_parameter(self, parameter_value): def check_handler_parameter(self, parameter_value):
if isinstance(parameter_value, str): if not isinstance(parameter_value, str):
return parameter_value raise IncorrectParameter("'handler' parameter must be string"
raise IncorrectParameter("'handler' parameter must be string" f" value not {type(parameter_value)}.")
f" value not {type(parameter_value)}.") return parameter_value
def check_notify_parameter(self, parameter_value): def check_notify_parameter(self, parameter_value):
if isinstance(parameter_value, list): if isinstance(parameter_value, list):
@ -516,6 +517,17 @@ class ParametersProcessor:
raise IncorrectParameter("'unbound' parameter is incompatible" raise IncorrectParameter("'unbound' parameter is incompatible"
" with 'autoupdate' parameter") " with 'autoupdate' parameter")
def check_postparse_handler(self, parameter_rvalue):
if self._parameters_container.merge:
raise IncorrectParameter("'merge' parameter is not available"
" in handler templates.")
elif (self._parameters_container.package and
not self._parameters_container.is_inherited('package')):
raise IncorrectParameter("'package' parameter is not available"
" in handler templates.")
# Методы для проверки того, являются ли параметры наследуемыми.
def is_chmod_inheritable(self, parameter_value): def is_chmod_inheritable(self, parameter_value):
chmod_regex = re.compile(r'\d+') chmod_regex = re.compile(r'\d+')
@ -739,6 +751,10 @@ class ParametersContainer(MutableMapping):
print('Inherited:') print('Inherited:')
pprint(self.__inheritable) pprint(self.__inheritable)
def is_inherited(self, parameter_name):
return (parameter_name not in self.__parameters
and parameter_name in self.__inheritable)
def _clear_container(self): def _clear_container(self):
self.__parameters.clear() self.__parameters.clear()
self.__inheritable.clear() self.__inheritable.clear()

@ -14,6 +14,7 @@ from calculate.variables.loader import Datavars
from .format.base_format import Format from .format.base_format import Format
from ..utils.io_module import IOModule from ..utils.io_module import IOModule
from collections import OrderedDict, abc from collections import OrderedDict, abc
from contextlib import contextmanager
from ..utils.mount import Mounts from ..utils.mount import Mounts
import hashlib import hashlib
import fnmatch import fnmatch
@ -357,7 +358,8 @@ class TemplateWrapper:
# Если для шаблона и целевого файла никаким образом не удается # Если для шаблона и целевого файла никаким образом не удается
# определить пакет и есть параметр append -- шаблон пропускаем. # определить пакет и есть параметр append -- шаблон пропускаем.
if parameter_package is None and file_package is None: if parameter_package is None and file_package is None:
if self.parameters.append and self.parameters.append != 'skip': if (self.parameters.append and self.parameters.append != 'skip'
and not self.parameters.handler):
raise TemplateCollisionError( raise TemplateCollisionError(
"'package' parameter is not defined for" "'package' parameter is not defined for"
" template with 'append' parameter.") " template with 'append' parameter.")
@ -367,6 +369,12 @@ class TemplateWrapper:
self.target_package_name = file_package self.target_package_name = file_package
elif file_package is None: elif file_package is None:
if self.parameters.handler:
raise TemplateCollisionError((
"The template is a handler while target"
" file belongs {} package").format(
file_package.atom
))
self.target_package_name = parameter_package self.target_package_name = parameter_package
elif file_package != parameter_package and self.template_type != DIR: elif file_package != parameter_package and self.template_type != DIR:
@ -393,7 +401,7 @@ class TemplateWrapper:
# Проверим, является ли файл защищенным. # Проверим, является ли файл защищенным.
# Сначала проверяем по переменной CONFIG_PROTECT. # Сначала проверяем по переменной CONFIG_PROTECT.
if self.dbpkg: if self.dbpkg and not self.parameters.handler:
for protected_path in self._protected_set: for protected_path in self._protected_set:
if self.target_path.startswith(protected_path): if self.target_path.startswith(protected_path):
self.protected = True self.protected = True
@ -1689,7 +1697,9 @@ class DirectoryProcessor:
self.packages_file_trees = {} self.packages_file_trees = {}
# Список обработчиков. # Список обработчиков.
self.handlers = OrderedDict() self._handlers = {}
self._handlers_queue = []
self._handling = None
def _get_cl_ignore_files(self) -> list: def _get_cl_ignore_files(self) -> list:
'''Метод для получения из соответствующей переменной списка паттернов '''Метод для получения из соответствующей переменной списка паттернов
@ -1756,11 +1766,69 @@ class DirectoryProcessor:
self.processed_packages.append(self.for_package) self.processed_packages.append(self.for_package)
self._merge_packages() self._merge_packages()
if self._handlers_queue:
self._execute_handlers()
if self.template_executor.execute_files: if self.template_executor.execute_files:
self._run_exec_files() self._run_exec_files()
self.template_executor.save_changes() self.template_executor.save_changes()
def _execute_handlers(self):
self.output.set_info('Processing handlers...')
print('CURRENT HANDLERS QUEUE:', ', '.join(self._handlers_queue))
index = 0
while index < len(self._handlers_queue):
handler = self._handlers_queue[index]
index += 1
if handler in self._handlers:
handler_type, handler_path = self._handlers[handler]
else:
self.output.set_warning(
f"Handler '{handler}' is not found.")
continue
with self._start_handling(handler):
if handler_type is DIR:
self.directory_tree = {}
self._walk_directory_tree(
handler_path,
self.cl_chroot_path,
ParametersContainer(),
directory_tree=self.directory_tree)
self.output.set_success(
f"Processed handler directory '{handler}'."
f"Path: '{handler_path}'")
elif handler_type is FILE:
handler_dir, handler_name = os.path.split(handler_path)
self.template_engine.change_directory(handler_dir)
parameters = ParametersContainer()
handler_text = self._parse_template(parameters,
handler_name,
FILE, handler_dir)
if handler_text is False:
continue
if parameters.notify:
for handler_id in parameters.notify:
if handler_id not in self._handlers_queue:
self._handlers_queue.append(handler_id)
# Корректируем путь к целевому файлу.
target_file_path = self._make_target_path(
self.cl_chroot_path,
handler_name,
parameters)
# Выполняем действия, указанные в обработчике.
self._execute_template(target_file_path, parameters,
FILE, handler_path,
template_text=handler_text)
self.output.set_success(
f"Processed handler file '{handler}'."
f" Path: '{handler_path}'")
def _merge_packages(self): def _merge_packages(self):
'''Метод для выполнения шаблонов относящихся к пакетам, указанным во '''Метод для выполнения шаблонов относящихся к пакетам, указанным во
всех встреченных значениях параметра merge.''' всех встреченных значениях параметра merge.'''
@ -1877,15 +1945,26 @@ class DirectoryProcessor:
# Если нужно заполнять дерево директорий, отправляем в метод для # Если нужно заполнять дерево директорий, отправляем в метод для
# проверки параметров package и action текущее дерево. # проверки параметров package и action текущее дерево.
if not self._check_package_and_action( if (self._handling is None
directory_parameters, and not self._check_package_and_action(
current_directory_path, directory_parameters,
directory_tree=(directory_tree if current_directory_path,
self.fill_trees else None)): directory_tree=(directory_tree if
# Если проверка не пройдена и включено заполнение дерева, то, self.fill_trees else None))):
# используя нынешнее состояние дерева директорий, обновляем if directory_parameters.handler:
# дерево пакета текущего шаблона директории. # Если директория шаблонов является обработчиком и параметр
if self.fill_trees: # handler -- не унаследован, что свидетельствует о том,
# что директория не расположена внутри другой
# директории-обработчика добавляем ее в словарь
# обработчиков и пропускаем ее.
self._handlers.update({directory_parameters.handler:
(DIR, current_directory_path)})
if self.fill_trees:
directory_tree = {}
elif self.fill_trees:
# Если проверка не пройдена и включено заполнение дерева,
# то, используя нынешнее состояние дерева директорий,
# обновляем дерево пакета текущего шаблона директории.
self._update_package_tree(directory_parameters.package, self._update_package_tree(directory_parameters.package,
directory_tree[directory_name]) directory_tree[directory_name])
# Перед выходом из директории очищаем текущий уровень # Перед выходом из директории очищаем текущий уровень
@ -1893,11 +1972,23 @@ class DirectoryProcessor:
directory_tree = {} directory_tree = {}
return return
if self._handling is not None and directory_parameters.handler:
if directory_parameters.handler != self._handling:
self.output.set_error("'handler' parameter is not"
" available in other handler"
f" '{self._handling}'")
return
# Если есть параметр merge -- сохраняем присутствующие в нем пакеты # Если есть параметр merge -- сохраняем присутствующие в нем пакеты
# для последующей обработки. # для последующей обработки.
if self.for_package and directory_parameters.merge: if self.for_package and directory_parameters.merge:
self.packages_to_merge.extend(directory_parameters.merge) self.packages_to_merge.extend(directory_parameters.merge)
if directory_parameters.notify:
for handler_id in directory_parameters.notify:
if handler_id not in self._handlers_queue:
self._handlers_queue.append(handler_id)
# Если присутствует параметр package -- проверяем, изменился ли он # Если присутствует параметр package -- проверяем, изменился ли он
# и был ли задан до этого. Если не был задан или изменился, меняем # и был ли задан до этого. Если не был задан или изменился, меняем
# текущий пакет ветки шаблонов на этот. # текущий пакет ветки шаблонов на этот.
@ -1909,20 +2000,6 @@ class DirectoryProcessor:
else: else:
# Если .calculate_directory отсутствует -- создаем директорию, # Если .calculate_directory отсутствует -- создаем директорию,
# используя унаследованные параметры и имя самой директории. # используя унаследованные параметры и имя самой директории.
if not self._check_package_and_action(
directory_parameters,
current_directory_path,
directory_tree=(directory_tree if
self.fill_trees else None)):
# Обновляем дерево директорий для данного пакета.
if self.fill_trees:
self._update_package_tree(directory_parameters.package,
directory_tree[directory_name])
# Перед выходом из директории очищаем текущий уровень
# дерева.
directory_tree = {}
return
# Для того чтобы директория была создана, просто добавляем параметр # Для того чтобы директория была создана, просто добавляем параметр
# append = join. # append = join.
directory_parameters.set_parameter({'append': 'join'}) directory_parameters.set_parameter({'append': 'join'})
@ -1971,18 +2048,36 @@ class DirectoryProcessor:
# Если находимся на стадии заполнения дерева директорий -- # Если находимся на стадии заполнения дерева директорий --
# проверяем параметры package и action с заполнением дерева. # проверяем параметры package и action с заполнением дерева.
if not self._check_package_and_action( if (self._handling is None
and not self._check_package_and_action(
template_parameters, template_parameters,
template_path, template_path,
directory_tree=(directory_tree[directory_name] if directory_tree=(directory_tree[directory_name] if
self.fill_trees else None)): self.fill_trees else None))):
if template_parameters.handler:
# Если директория шаблонов является обработчиком --
# добавляем ее в словарь обработчиков и пропускаем ее.
self._handlers.update({template_parameters.handler:
(FILE, template_path)})
continue continue
if self._handling is not None and template_parameters.handler:
if template_parameters.handler != self._handling:
self.output.set_error("'handler' parameter is not"
" available in other handler"
f" '{self._handling}'")
continue
# Если есть параметр merge добавляем его содержимое в список # Если есть параметр merge добавляем его содержимое в список
# пакетов для последующей обработки. # пакетов для последующей обработки.
if self.for_package and template_parameters.merge: if self.for_package and template_parameters.merge:
self.packages_to_merge.extend(template_parameters.merge) self.packages_to_merge.extend(template_parameters.merge)
if template_parameters.notify:
for handler_id in template_parameters.notify:
if handler_id not in self._handlers_queue:
self._handlers_queue.append(handler_id)
# Корректируем путь к целевому файлу. # Корректируем путь к целевому файлу.
target_file_path = self._make_target_path(current_target_path, target_file_path = self._make_target_path(current_target_path,
template_name, template_name,
@ -2216,6 +2311,12 @@ class DirectoryProcessor:
каталогов с шаблонами. Если среди аргументов указано также каталогов с шаблонами. Если среди аргументов указано также
дерево каталогов, то в случае несовпадения значений package для файла дерево каталогов, то в случае несовпадения значений package для файла
или директории, им в дереве присваивается значение None.''' или директории, им в дереве присваивается значение None.'''
if parameters.handler:
print('\nHANDLER WAS FOUND:', template_path)
print('NOW HANDLERS:', *list(self._handlers.keys()))
print()
return False
if parameters.append != 'skip' or parameters.action: if parameters.append != 'skip' or parameters.action:
if not parameters.action: if not parameters.action:
self.output.set_warning( self.output.set_warning(
@ -2233,10 +2334,6 @@ class DirectoryProcessor:
template_path)) template_path))
return False return False
if parameters.handler:
self.handlers.update({parameters.handler: template_path})
return False
if self.for_package: if self.for_package:
if not parameters.package: if not parameters.package:
if self.for_package is not NonePackage: if self.for_package is not NonePackage:
@ -2260,3 +2357,11 @@ class DirectoryProcessor:
return False return False
return True return True
@contextmanager
def _start_handling(self, handler):
try:
self._handling = handler
yield self
finally:
self._handling = None

@ -895,28 +895,85 @@ class TestDirectoryProcessor:
assert '/etc/dir_35/file_0' in test_package assert '/etc/dir_35/file_0' in test_package
def test_handlers_basic(self): def test_handlers_basic(self):
datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH, try:
'templates_35') datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH,
directory_processor = DirectoryProcessor( 'templates_35')
'install', directory_processor = DirectoryProcessor(
datavars_module=datavars, 'install',
package='test-category/test-package' datavars_module=datavars,
) package='test-category/test-package'
directory_processor.process_template_directories() )
test_package = Package(test_package_name, chroot_path=CHROOT_PATH) directory_processor.process_template_directories()
other_package = Package(other_package_name, chroot_path=CHROOT_PATH) test_package = Package(test_package_name, chroot_path=CHROOT_PATH)
assert os.path.exists(join_paths(CHROOT_PATH, # Тестируем handler_0 -- это просто обработчик-скрипт.
'etc/dir_37')) assert os.path.exists(join_paths(CHROOT_PATH,
assert os.path.exists(join_paths(CHROOT_PATH, 'etc/dir_37'))
'etc/dir_37/file_0')) assert os.path.exists(join_paths(CHROOT_PATH,
assert '/etc/dir_37/file_0' in test_package 'etc/dir_37/file_0'))
assert '/etc/dir_37/file_0' in test_package
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_38')) # Тестируем handler_0 -- это просто обработчик-шаблон с notify.
assert os.path.exists(join_paths(CHROOT_PATH, assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_38/file_0')) 'etc/dir_38'))
assert '/etc/dir_38/file_0' in other_package assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_38/file_0'))
# Тестируем handler_1 -- это просто обработчик-шаблон с notify.
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_39'))
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_39/file_0'))
# Тестируем handler_2 -- это просто обработчик-скрипт с notify.
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_40'))
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_40/file_0'))
# Тестируем handler_3 -- это обработчик-шаблон с notify к
# вызвавшему его handler_2 и handler_4, спрятанный в директории,
# не являющейся обработчиком.
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_41'))
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_41/file_0'))
# Тестируем handler_4 -- это обработчик с exec и notify.
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_42'))
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_42/file_0'))
# Тестируем handler_5 -- это обработчик-директория.
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_43'))
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_43/file_0'))
# Тестируем handler_6 -- это обработчик из обработчика-директрии.
# Этот обработчик не выполняется.
assert not os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_44'))
# Тестируем handler_7 -- это обработчик с параметром package.
# Этот обработчик не выполняется.
assert not os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_45'))
# Тестируем handler_8 -- это обработчик с параметром merge.
# Этот обработчик не выполняется.
assert not os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_46'))
# Тестируем handler_9 -- это обработчик замещенный другим.
# Этот обработчик не выполняется.
assert not os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_47'))
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_48'))
except KeyboardInterrupt:
assert False
def test_view_tree(self): def test_view_tree(self):
list_path = join_paths(CHROOT_PATH, '/etc') list_path = join_paths(CHROOT_PATH, '/etc')

@ -1,4 +1,5 @@
{% calculate append = 'join', format = 'bind', notify = 'handler_0' %} {% calculate append = 'join', format = 'bind', notify = ['handler_0',
'handler_1'] %}
options { options {
parameter-1 {{ variables.variable_1 }}; parameter-1 {{ variables.variable_1 }};
parameter-2 {{ variables.variable_2 }}; parameter-2 {{ variables.variable_2 }};

@ -0,0 +1,5 @@
{% calculate handler = 'handler_3', append = 'join', path = '/etc/dir_41/',
name = 'file_0', format = 'samba', notify = 'handler_2, handler_4' %}
[section 1]
parameter 1 = {{ variables.variable_1 }}
parameter 2 = {{ variables.variable_2 }}

@ -0,0 +1,5 @@
{% calculate handler = 'handler_9', append = 'join', path = '/etc/dir_48/',
name = 'file_0', format = 'samba' %}
[section 1]
parameter 1 = {{ variables.variable_1 }}
parameter 2 = {{ variables.variable_2 }}

@ -1 +0,0 @@
{% calculate append = 'join', package = 'test-category/other-package' %}

@ -1,5 +0,0 @@
{% calculate append = 'join', format = 'bind' %}
options {
parameter-1 {{ variables.variable_1 }};
parameter-2 {{ variables.variable_2 }};
};

@ -1,3 +1,5 @@
{% calculate handler = 'handler_0', merge = 'test-category/other-package', {% calculate handler = 'handler_0', path = '/etc', run = '/usr/bin/python' %}
run = '/usr/bin/python' %} import os
print('This handler is needed for merging test-category.') os.mkdir('./dir_38')
with open('./dir_38/file_0', 'w') as file_0:
file_0.write('Information number 1.')

@ -0,0 +1,5 @@
{% calculate handler = 'handler_1', append = 'join', path = '/etc/dir_39/',
name = 'file_0', format = 'samba', notify = 'handler_2' %}
[section 1]
parameter 1 = {{ variables.variable_1 }}
parameter 2 = {{ variables.variable_2 }}

@ -0,0 +1,6 @@
{% calculate handler = 'handler_2', path = '/etc', run = '/usr/bin/python',
notify = 'handler_3' %}
import os
os.mkdir('./dir_40')
with open('./dir_40/file_0', 'w') as file_0:
file_0.write('Information number 2.')

@ -0,0 +1,6 @@
{% calculate handler = 'handler_4', path = '/etc', exec = '/usr/bin/python',
notify = 'handler_5' %}
import os
os.mkdir('./dir_42')
with open('./dir_42/file_0', 'w') as file_0:
file_0.write('Information number 2.')

@ -0,0 +1,2 @@
{% calculate handler = 'handler_5', path = '/etc', name = 'dir_43',
notify = 'handler_9' %}

@ -0,0 +1,5 @@
{% calculate append = 'join', format = 'samba',
notify = 'handler_6, handler_7' %}
[section 1]
parameter 1 = {{ variables.variable_1 }}
parameter 2 = {{ variables.variable_2 }}

@ -0,0 +1,6 @@
{% calculate handler = 'handler_6', path = '/etc', run = '/usr/bin/python',
notify = 'handler_3' %}
import os
os.mkdir('./dir_44')
with open('./dir_44/file_0', 'w') as file_0:
file_0.write('Information number 3.')

@ -0,0 +1,5 @@
{% calculate handler = 'handler_7', append = 'join', path = '/etc/dir_45/',
name = 'file_0', format = 'samba', package = 'test-category/test-package' %}
[section 1]
parameter 1 = {{ variables.variable_1 }}
parameter 2 = {{ variables.variable_2 }}

@ -0,0 +1,5 @@
{% calculate handler = 'handler_8', append = 'join', path = '/etc/dir_46/',
name = 'file_0', format = 'samba', merge = 'test-category/test-package' %}
[section 1]
parameter 1 = {{ variables.variable_1 }}
parameter 2 = {{ variables.variable_2 }}

@ -0,0 +1,5 @@
{% calculate handler = 'handler_9', append = 'join', path = '/etc/dir_48/',
name = 'file_0', format = 'samba' %}
[section 1]
parameter 1 = {{ variables.variable_1 }}
parameter 2 = {{ variables.variable_2 }}
Loading…
Cancel
Save