2022-10-01 22:33:11 +02:00
|
|
|
#!/usr/bin/python3
|
2023-01-23 20:23:32 +01:00
|
|
|
from os import readlink, walk, chdir, getcwd, makedirs
|
|
|
|
from os.path import join, islink, isdir
|
2023-06-22 15:49:09 +02:00
|
|
|
from typing import Dict, Any
|
|
|
|
from shutil import rmtree, copy2
|
2023-01-23 20:23:32 +01:00
|
|
|
import tarfile
|
2023-06-22 15:49:09 +02:00
|
|
|
from ansible.module_utils._text import to_text
|
|
|
|
from ansible import constants
|
|
|
|
|
|
|
|
from rougail.template import base
|
|
|
|
from rougail.error import TemplateError
|
|
|
|
from risotto.machine import build_files, INSTALL_DIR, INSTALL_CONFIG_DIR, INSTALL_TMPL_DIR, INSTALL_IMAGES_DIR, INSTALL_TESTS_DIR
|
|
|
|
from risotto.utils import custom_filters
|
2023-02-27 14:03:56 +01:00
|
|
|
|
|
|
|
|
2022-12-21 16:35:58 +01:00
|
|
|
try:
|
|
|
|
from ansible.plugins.action import ActionBase
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
class FakeModule(AnsibleModule):
|
|
|
|
def __init__(self):
|
|
|
|
pass
|
2023-06-22 15:49:09 +02:00
|
|
|
from ansible.plugins.action.template import ActionModule as TmplActionModule
|
2022-12-21 16:35:58 +01:00
|
|
|
except:
|
|
|
|
class ActionBase():
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
raise Exception('works only with ansible')
|
2022-10-01 22:33:11 +02:00
|
|
|
|
|
|
|
|
2023-01-23 20:23:32 +01:00
|
|
|
ARCHIVES_DIR = '/tmp/new_configurations'
|
|
|
|
|
|
|
|
|
2023-02-27 14:03:56 +01:00
|
|
|
def is_diff(server_name,
|
|
|
|
remote_directories,
|
|
|
|
certificates,
|
|
|
|
):
|
2023-01-23 20:23:32 +01:00
|
|
|
ret = {}
|
|
|
|
module = FakeModule()
|
|
|
|
current_path = getcwd()
|
|
|
|
root = join(INSTALL_DIR, INSTALL_CONFIG_DIR, server_name)
|
|
|
|
chdir(root)
|
|
|
|
search_paths = [join(directory[2:], f) for directory, subdirectories, files in walk('.') for f in files]
|
|
|
|
chdir(current_path)
|
|
|
|
for path in search_paths:
|
|
|
|
if path not in remote_directories:
|
|
|
|
return True
|
|
|
|
full_path = join(root, path)
|
|
|
|
if not islink(full_path):
|
|
|
|
if remote_directories[path] != module.digest_from_file(full_path, 'sha256'):
|
|
|
|
return True
|
|
|
|
elif remote_directories[path] != readlink(full_path):
|
|
|
|
return True
|
|
|
|
remote_directories.pop(path)
|
|
|
|
if remote_directories:
|
2023-02-27 14:03:56 +01:00
|
|
|
for certificate in certificates:
|
|
|
|
for typ in ['name', 'private', 'authority']:
|
|
|
|
if not typ in certificate:
|
|
|
|
continue
|
|
|
|
name = certificate[typ][1:]
|
|
|
|
if name in remote_directories:
|
|
|
|
remote_directories.pop(name)
|
|
|
|
if remote_directories:
|
|
|
|
return True
|
2023-01-23 20:23:32 +01:00
|
|
|
return False
|
2022-10-01 22:33:11 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ActionModule(ActionBase):
|
|
|
|
def run(self, tmp=None, task_vars=None):
|
|
|
|
super(ActionModule, self).run(tmp, task_vars)
|
|
|
|
module_args = self._task.args.copy()
|
2023-01-23 20:23:32 +01:00
|
|
|
hostname = module_args.pop('hostname')
|
|
|
|
only_machine = module_args.pop('only_machine')
|
|
|
|
configure_host = module_args.pop('configure_host')
|
|
|
|
copy_tests = module_args.pop('copy_tests')
|
2023-06-22 15:49:09 +02:00
|
|
|
# define ansible engine
|
|
|
|
base.ENGINES['ansible'] = Tmpl(task_vars,
|
|
|
|
self._task,
|
|
|
|
self._connection,
|
|
|
|
self._play_context,
|
|
|
|
self._loader,
|
|
|
|
self._templar,
|
|
|
|
self._shared_loader_obj,
|
|
|
|
)
|
2023-01-23 20:23:32 +01:00
|
|
|
if 'copy_templates' in module_args:
|
|
|
|
copy_templates = module_args.pop('copy_templates')
|
|
|
|
else:
|
|
|
|
copy_templates = False
|
2023-06-22 15:49:09 +02:00
|
|
|
directories, certificates = build_files(hostname,
|
|
|
|
only_machine,
|
|
|
|
False,
|
|
|
|
copy_tests,
|
|
|
|
)
|
2023-01-23 20:23:32 +01:00
|
|
|
module_args['directories'] = list(directories.values())
|
|
|
|
module_args['directories'].append('/var/lib/risotto/images_files')
|
|
|
|
remote = self._execute_module(module_name='compare',
|
|
|
|
module_args=module_args,
|
|
|
|
task_vars=task_vars,
|
|
|
|
)
|
2022-12-21 16:35:58 +01:00
|
|
|
if remote.get('failed'):
|
2023-01-23 20:23:32 +01:00
|
|
|
if 'module_stdout' in remote:
|
|
|
|
msg = remote['module_stdout']
|
|
|
|
else:
|
|
|
|
msg = remote['msg']
|
|
|
|
raise Exception(f'error in remote action: {msg}')
|
|
|
|
if copy_templates:
|
2023-06-22 15:49:09 +02:00
|
|
|
build_files(hostname,
|
|
|
|
only_machine,
|
|
|
|
True,
|
|
|
|
copy_tests,
|
|
|
|
)
|
2023-01-23 20:23:32 +01:00
|
|
|
|
|
|
|
machines_changed = []
|
|
|
|
for machine, directory in directories.items():
|
|
|
|
if directory not in remote['directories']:
|
|
|
|
machines_changed.append(machine)
|
|
|
|
continue
|
2023-02-27 14:03:56 +01:00
|
|
|
if is_diff(machine,
|
|
|
|
remote['directories'][directory],
|
|
|
|
certificates['certificates'].get(machine, []),
|
|
|
|
):
|
2023-01-23 20:23:32 +01:00
|
|
|
machines_changed.append(machine)
|
|
|
|
current_path = getcwd()
|
|
|
|
if isdir(ARCHIVES_DIR):
|
|
|
|
rmtree(ARCHIVES_DIR)
|
|
|
|
makedirs(ARCHIVES_DIR)
|
|
|
|
if machines_changed:
|
|
|
|
self._execute_module(module_name='file',
|
|
|
|
module_args={'path': ARCHIVES_DIR,
|
|
|
|
'state': 'absent',
|
|
|
|
},
|
|
|
|
task_vars=task_vars,
|
|
|
|
)
|
|
|
|
self._execute_module(module_name='file',
|
|
|
|
module_args={'path': ARCHIVES_DIR,
|
|
|
|
'state': 'directory',
|
|
|
|
},
|
|
|
|
task_vars=task_vars,
|
|
|
|
)
|
|
|
|
machines = machines_changed.copy()
|
|
|
|
if self._task.args['hostname'] in machines_changed:
|
|
|
|
machine = self._task.args['hostname']
|
|
|
|
machines.remove(machine)
|
|
|
|
chdir(f'{task_vars["host_install_dir"]}/{INSTALL_CONFIG_DIR}/{machine}')
|
|
|
|
tar_filename = f'{ARCHIVES_DIR}/host.tar'
|
|
|
|
with tarfile.open(tar_filename, 'w') as archive:
|
|
|
|
archive.add('.')
|
|
|
|
chdir(current_path)
|
|
|
|
self._transfer_file(tar_filename, tar_filename)
|
|
|
|
|
|
|
|
# archive and send
|
|
|
|
if machines:
|
|
|
|
chdir(f'{task_vars["host_install_dir"]}/{INSTALL_CONFIG_DIR}')
|
|
|
|
tar_filename = f'{ARCHIVES_DIR}/machines.tar'
|
|
|
|
with tarfile.open(tar_filename, 'w') as archive:
|
|
|
|
for machine in machines:
|
|
|
|
if machine == self._task.args['hostname']:
|
|
|
|
continue
|
|
|
|
archive.add(f'{machine}')
|
|
|
|
self._transfer_file(tar_filename, tar_filename)
|
|
|
|
else:
|
|
|
|
machines = []
|
|
|
|
# archive and send
|
|
|
|
chdir(f'{task_vars["host_install_dir"]}/{INSTALL_IMAGES_DIR}/')
|
|
|
|
tar_filename = f'{ARCHIVES_DIR}/{INSTALL_IMAGES_DIR}.tar'
|
|
|
|
with tarfile.open(tar_filename, 'w') as archive:
|
|
|
|
archive.add('.')
|
2023-06-22 15:49:09 +02:00
|
|
|
self._execute_module(module_name='file',
|
|
|
|
module_args={'path': '/tmp/new_configurations',
|
|
|
|
'state': 'directory',
|
|
|
|
},
|
|
|
|
task_vars=task_vars,
|
|
|
|
)
|
2023-01-23 20:23:32 +01:00
|
|
|
self._transfer_file(tar_filename, tar_filename)
|
|
|
|
# tests
|
|
|
|
self._execute_module(module_name='file',
|
|
|
|
module_args={'path': '/var/lib/risotto/tests',
|
|
|
|
'state': 'absent',
|
|
|
|
},
|
|
|
|
task_vars=task_vars,
|
|
|
|
)
|
|
|
|
if copy_tests:
|
|
|
|
chdir(f'{task_vars["host_install_dir"]}/{INSTALL_TESTS_DIR}/')
|
|
|
|
tar_filename = f'{ARCHIVES_DIR}/{INSTALL_TESTS_DIR}.tar'
|
|
|
|
with tarfile.open(tar_filename, 'w') as archive:
|
|
|
|
archive.add('.')
|
|
|
|
self._transfer_file(tar_filename, tar_filename)
|
|
|
|
# templates
|
|
|
|
self._execute_module(module_name='file',
|
|
|
|
module_args={'path': '/var/lib/risotto/templates',
|
|
|
|
'state': 'absent',
|
|
|
|
},
|
|
|
|
task_vars=task_vars,
|
|
|
|
)
|
|
|
|
if copy_templates:
|
|
|
|
chdir(f'{task_vars["host_install_dir"]}/')
|
|
|
|
tar_filename = f'{ARCHIVES_DIR}/{INSTALL_TMPL_DIR}.tar'
|
|
|
|
with tarfile.open(tar_filename, 'w') as archive:
|
|
|
|
archive.add(INSTALL_TMPL_DIR)
|
|
|
|
self._transfer_file(tar_filename, tar_filename)
|
|
|
|
remote = self._execute_module(module_name='unarchive',
|
|
|
|
module_args={'remote_src': True,
|
|
|
|
'src': '/tmp/new_configurations/templates.tar',
|
|
|
|
'dest': '/var/lib/risotto',
|
|
|
|
},
|
|
|
|
task_vars=task_vars,
|
|
|
|
)
|
|
|
|
chdir(current_path)
|
|
|
|
changed = machines_changed != []
|
2023-02-27 14:03:56 +01:00
|
|
|
return dict(ansible_facts=dict({}),
|
|
|
|
changed=changed,
|
|
|
|
machines_changed=machines,
|
|
|
|
host_changed=self._task.args['hostname'] in machines_changed,
|
|
|
|
)
|
2023-06-22 15:49:09 +02:00
|
|
|
|
|
|
|
|
|
|
|
class FakeCopy:
|
|
|
|
def __init__(self, task):
|
|
|
|
self.task = task
|
|
|
|
|
|
|
|
def run(self, *args, **kwargs):
|
|
|
|
copy2(self.task.args['src'], self.task.args['dest'])
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
class FakeGet:
|
|
|
|
def __init__(self, klass):
|
|
|
|
self.klass = klass
|
|
|
|
|
|
|
|
def fake_get(self, action, *args, task, **kwargs):
|
|
|
|
if action == 'ansible.legacy.copy':
|
|
|
|
return FakeCopy(task)
|
|
|
|
return self.klass.ori_get(action, *args, task=task, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class Tmpl(TmplActionModule):
|
|
|
|
def __init__(self, task_vars, *args):
|
|
|
|
super().__init__(*args)
|
|
|
|
self.task_vars = task_vars
|
|
|
|
|
|
|
|
def _early_needs_tmp_path(self):
|
|
|
|
# do not create tmp remotely
|
|
|
|
return False
|
|
|
|
|
|
|
|
def process(self,
|
|
|
|
filename: str,
|
|
|
|
source: str,
|
|
|
|
true_destfilename: str,
|
|
|
|
destfilename: str,
|
|
|
|
destdir: str,
|
|
|
|
variable: Any,
|
|
|
|
index: int,
|
|
|
|
rougail_variables_dict: Dict,
|
|
|
|
eosfunc: Dict,
|
|
|
|
extra_variables: Any=None,
|
|
|
|
):
|
|
|
|
if source is not None: # pragma: no cover
|
|
|
|
raise TemplateError(_('source is not supported for ansible'))
|
|
|
|
task_vars = rougail_variables_dict | self.task_vars
|
|
|
|
if variable is not None:
|
|
|
|
task_vars['rougail_variable'] = variable
|
|
|
|
if index is not None:
|
|
|
|
task_vars['rougail_index'] = index
|
|
|
|
if extra_variables:
|
|
|
|
task_vars['extra_variables'] = extra_variables
|
|
|
|
task_vars['rougail_filename'] = true_destfilename
|
|
|
|
task_vars['rougail_destination_dir'] = destdir
|
|
|
|
self._task.args['src'] = filename
|
|
|
|
self._task.args['dest'] = destfilename
|
|
|
|
# add custom filter
|
|
|
|
custom_filters.update(eosfunc)
|
|
|
|
# do not copy file in host but stay it locally
|
|
|
|
self._shared_loader_obj.action_loader.ori_get = self._shared_loader_obj.action_loader.get
|
|
|
|
self._shared_loader_obj.action_loader.get = FakeGet(self._shared_loader_obj.action_loader).fake_get
|
|
|
|
# template
|
|
|
|
ret = self.run(task_vars=task_vars)
|
|
|
|
# restore get function
|
|
|
|
self._shared_loader_obj.action_loader.get = self._shared_loader_obj.action_loader.ori_get
|
|
|
|
# remove custom filter
|
|
|
|
custom_filters.clear()
|
|
|
|
if ret.get('failed'):
|
|
|
|
raise TemplateError(f'error while templating "{filename}": {ret["msg"]}')
|