#!/usr/bin/python3 from asyncio import run from os import readlink, walk, chdir, getcwd, makedirs from os.path import join, islink, isdir from risotto.machine import load, templates, INSTALL_DIR, INSTALL_CONFIG_DIR, INSTALL_TMPL_DIR, INSTALL_IMAGES_DIR, INSTALL_TESTS_DIR from rougail.utils import normalize_family from shutil import rmtree import tarfile try: from ansible.plugins.action import ActionBase from ansible.module_utils.basic import AnsibleModule class FakeModule(AnsibleModule): def __init__(self): pass except: class ActionBase(): def __init__(self, *args, **kwargs): raise Exception('works only with ansible') ARCHIVES_DIR = '/tmp/new_configurations' async def build_files(hostname: str, only_machine: str, just_copy: bool, copy_tests: bool, ) -> None: config = await load(copy_tests=copy_tests) if only_machine: machines = [only_machine] else: machines = [await subconfig.option.description() for subconfig in await config.option.list(type='optiondescription')] # shasums = {} directories = {} for machine in machines: if just_copy and hostname == machine: continue await templates(machine, config, just_copy=just_copy, copy_manuals=True, ) #FIXME dest_dir? is_host = machine == hostname # shasums[machine] = {'shasums': get_shasums(dest_dir, is_host)} if is_host: # shasums[machine]['config_dir'] = '/usr/local/lib' directories[machine] = '/usr/local/lib' else: # shasums[machine]['config_dir'] = await config.option(normalize_family(machine)).option('general.config_dir').value.get() directories[machine] = await config.option(normalize_family(machine)).option('general.config_dir').value.get() return directories def is_diff(server_name, remote_directories): 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: return True return False class ActionModule(ActionBase): def run(self, tmp=None, task_vars=None): super(ActionModule, self).run(tmp, task_vars) module_args = self._task.args.copy() 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') if 'copy_templates' in module_args: copy_templates = module_args.pop('copy_templates') else: copy_templates = False directories = run(build_files(hostname, only_machine, False, copy_tests, )) 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, ) if remote.get('failed'): if 'module_stdout' in remote: msg = remote['module_stdout'] else: msg = remote['msg'] raise Exception(f'error in remote action: {msg}') if copy_templates: run(build_files(hostname, only_machine, True, copy_tests, )) machines_changed = [] for machine, directory in directories.items(): if directory not in remote['directories']: machines_changed.append(machine) continue if is_diff(machine, remote['directories'][directory]): 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('.') 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 != [] return dict(ansible_facts=dict({}), changed=changed, machines_changed=machines, host_changed=self._task.args['hostname'] in machines_changed)