From e3bca44f3a72a19d88928d091bb320cdb17a0a98 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sun, 21 Aug 2022 19:03:38 +0200 Subject: [PATCH] only one config --- bootstrap.py | 112 ++++--- funcs.py | 21 +- src/risotto/image.py | 208 ++++++------- src/risotto/machine.py | 510 +++++++------------------------ src/risotto/rougail/annotator.py | 368 +++++++++++++++++++--- src/risotto/utils.py | 16 +- src/risotto/x509.py | 2 +- 7 files changed, 622 insertions(+), 615 deletions(-) diff --git a/bootstrap.py b/bootstrap.py index f3cd8d8..02f6ea8 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -6,9 +6,10 @@ from os.path import isdir, join from shutil import rmtree from copy import copy -from risotto.utils import CONFIGS, RISOTTO_CONFIG, SERVERS -from risotto.image import load -from risotto.machine import templates +from risotto.utils import RISOTTO_CONFIG, SERVERS +#from risotto.image import load +from risotto.machine import templates, load, ROUGAIL_NAMESPACE +from rougail.utils import normalize_family INSTALL_DIR = RISOTTO_CONFIG['directories']['dest'] @@ -34,53 +35,84 @@ async def main(): if isdir(INSTALL_DIR): rmtree(INSTALL_DIR) makedirs(INSTALL_DIR) - module_infos = await load(display_name=tiramisu_display_name, clean_directories=True, copy_manual_dir=True) + try: + module_infos, rougailconfig, config = await load(display_name=tiramisu_display_name, + clean_directories=True, + copy_manual_dir=True, + copy_tests=True, + ) + except Exception as err: +# import traceback +# traceback.print_exc() + print(err) + exit(1) + modules_done = [] for server_name in SERVERS: - module_name = CONFIGS[server_name]['module_name'] - add_srv = CONFIGS[server_name]['add_srv'] - cfg = CONFIGS[server_name]['templates_informations'] - cfg['tmp_dir'] = 'tmp' - cfg['destinations_dir'] = join(INSTALL_DIR, module_name, CONFIG_DEST_DIR, server_name) + module_name = SERVERS[server_name]['module'] + module_info = module_infos[module_name] + subconfig = config.option(normalize_family(server_name)) + try: + add_srv = await subconfig.option('machine.add_srv').value.get() + except AttributeError: + add_srv = False + rougailconfig['tmp_dir'] = 'tmp' + rougailconfig['destinations_dir'] = join(INSTALL_DIR, module_name, CONFIG_DEST_DIR, server_name) + rougailconfig['templates_dir'] = module_info['infos'].templates_dir + if module_name == 'host': + tmpfile = await subconfig.option(f'{ROUGAIL_NAMESPACE}.host_install_dir').value.get() + rougailconfig['tmpfile_dest_dir'] = f'{tmpfile}/host/configurations/{server_name}' + rougailconfig['default_systemd_directory'] = '/usr/local/lib/systemd' + else: + rougailconfig['tmpfile_dest_dir'] = '/usr/local/lib' + rougailconfig['default_systemd_directory'] = '/systemd' +# cfg['templates_dir'] = module_info['infos'].templates_dir if isdir('tmp'): rmtree('tmp') - makedirs(cfg['tmp_dir']) - makedirs(cfg['destinations_dir']) + makedirs(rougailconfig['tmp_dir']) + makedirs(rougailconfig['destinations_dir']) if add_srv: srv = join(INSTALL_DIR, SRV_DEST_DIR, server_name) else: srv = None - await templates(server_name, **CONFIGS[server_name], srv=srv) - for server_name in SERVERS: - config = CONFIGS[server_name]['config'] + await templates(server_name, + subconfig, + rougailconfig, + srv=srv, + ) + # await config.property.read_write() try: -# pass - await config.option('general.hide_secret').value.set(True) - except AttributeError: - # if rougail.general.hide_secret not exists + await subconfig.option('general.hide_secret').value.set(True) + except AttributeError as err: + # print(err) pass await config.property.read_only() - for server_name in SERVERS: - config = CONFIGS[server_name]['config'] - await config.value.dict() - for server_name in SERVERS: - module_name = CONFIGS[server_name]['module_name'] - destinations_dir = join(INSTALL_DIR, module_name, CONFIG_DIFF_DIR, server_name) - makedirs(destinations_dir) - CONFIGS[server_name]['templates_informations']['destinations_dir'] = destinations_dir - await templates(server_name, **CONFIGS[server_name]) - for module_name, cfg in module_infos.items(): + rougailconfig['destinations_dir'] = join(INSTALL_DIR, module_name, CONFIG_DIFF_DIR, server_name) + rmtree('tmp') + makedirs(rougailconfig['tmp_dir']) + makedirs(rougailconfig['destinations_dir']) + await templates(server_name, + subconfig, + rougailconfig, + ) + await config.property.read_write() + try: + await subconfig.option('general.hide_secret').value.set(False) + except AttributeError as err: + pass + await config.property.read_only() + # + if module_name not in modules_done: + rougailconfig['destinations_dir'] = join(INSTALL_DIR, module_name, CONFIG_ORI_DIR) + rmtree('tmp') + makedirs(rougailconfig['tmp_dir']) + makedirs(rougailconfig['destinations_dir']) + await templates(server_name, + subconfig, + rougailconfig, + just_copy=True, + ) + modules_done.append(module_name) with open(join(INSTALL_DIR, module_name, 'install_machines'), 'w') as fh: - for idx, server_name in enumerate(cfg['infos'].servers): - if not idx: - destinations_dir = join(INSTALL_DIR, module_name, CONFIG_ORI_DIR) - makedirs(destinations_dir) - CONFIGS[server_name]['templates_informations']['destinations_dir'] = destinations_dir - await templates(server_name, - **CONFIGS[server_name], - just_copy=True, - ) - fh.write(f'./install_machine {module_name} {server_name}\n') - - + fh.write(f'./install_machine {module_name} {server_name}\n') run(main()) diff --git a/funcs.py b/funcs.py index 3997858..97dc833 100644 --- a/funcs.py +++ b/funcs.py @@ -5,7 +5,7 @@ from secrets import token_urlsafe as _token_urlsafe from rougail.utils import normalize_family -from risotto.utils import multi_function, DOMAINS, ZONES, load_zones, load_zones_server, load_domains, ZONES_SERVER +from risotto.utils import multi_function, DOMAINS, ZONES, load_zones, load_zones_server, load_domains, SERVERS_JSON from risotto.x509 import gen_cert as _x509_gen_cert, gen_ca as _x509_gen_ca, gen_pub as _x509_gen_pub, has_pub as _x509_has_pub @@ -19,7 +19,7 @@ def get_chain(authority_cn: str, ): if hide: return "XXXXX" - if not authority_name or authority_name is None: + if not authority_cn or not authority_name or authority_name is None: if isinstance(authority_name, list): return [] return @@ -142,7 +142,7 @@ def get_internal_zones() -> List[str]: def get_zones_info(type: str) -> str: load_zones_server() ret = [] - for data in ZONES_SERVER['zones'].values(): + for data in SERVERS_JSON['zones'].values(): ret.append(data[type]) return ret @@ -160,19 +160,4 @@ def get_internal_zone_information(zone: str, return ZONES[zone]['gateway'] + '/' + ZONES[zone]['network'].split('/')[-1] return ZONES[zone][info] - -def get_internal_info_in_zone(zone: str, - auto: bool, - type: str, - index: int=None, - ) -> List[str]: - if not auto: - return - for domain_name, domain in DOMAINS.items(): - if zone == domain_name: - if type == 'host': - return list(domain[0]) - else: - return domain[1][index] - # ============================================================= diff --git a/src/risotto/image.py b/src/risotto/image.py index e6a73cf..dd7d62c 100644 --- a/src/risotto/image.py +++ b/src/risotto/image.py @@ -1,25 +1,30 @@ from shutil import copy2, copytree, rmtree from os import listdir, makedirs -from os.path import join, isdir, isfile +from os.path import join, isdir, isfile, dirname from yaml import load as yaml_load, SafeLoader from json import load as json_load - -from .utils import CONFIGS, RISOTTO_CONFIG, SERVERS, ZONES_SERVER, value_pprint -from .machine import load_machine_config +# +from rougail import RougailConfig # , RougailConvert +# +from .utils import RISOTTO_CONFIG, SERVERS, MULTI_FUNCTIONS -FUNCTIONS = 'funcs.py' +FUNCTIONS_FILE = 'funcs.py' class ModuleCfg(): - def __init__(self): + def __init__(self, module_name): + self.module_name = module_name self.dictionaries_dir = [] self.modules = [] - self.functions_file = [FUNCTIONS] + self.functions_file = [FUNCTIONS_FILE] self.templates_dir = [] self.extra_dictionaries = {} self.servers = [] + def __repr__(self): + return str(vars(self)) + def list_applications() -> dict: """ @@ -40,8 +45,6 @@ def list_applications() -> dict: def applicationservice_copy(src_file: str, dst_file: str, copy_if_not_exists: bool, - manual_dir: str, - filename: str, ) -> None: if isdir(src_file): if not isdir(dst_file): @@ -55,12 +58,13 @@ def applicationservice_copy(src_file: str, else: copytree(src, dst) elif not copy_if_not_exists or not isfile(dst_file): - src = join(manual_dir, filename) - dst = dst_file - if isfile(src): - copy2(src, dst) + dst = dirname(dst_file) + if not isdir(dst): + makedirs(dst) + if isfile(src_file): + copy2(src_file, dst_file) else: - copytree(src, dst) + copytree(src_file, dst_file) def load_applicationservice_cfg(appname: str, @@ -68,6 +72,7 @@ def load_applicationservice_cfg(appname: str, install_dir: str, cfg: ModuleCfg, copy_manual_dir: bool, + copy_tests: bool, ) -> None: cfg.modules.append(appname) # dictionaries @@ -109,8 +114,16 @@ def load_applicationservice_cfg(appname: str, applicationservice_copy(src_file, dst_file, copy_if_not_exists, - manual_dir, - filename, + ) + if copy_tests: + tests_dir = join(as_dir, 'tests') + if isdir(tests_dir): + for filename in listdir(tests_dir): + src_file = join(tests_dir, filename) + dst_file = join(install_dir, 'tests', filename) + applicationservice_copy(src_file, + dst_file, + False, ) @@ -120,10 +133,12 @@ def load_applicationservice(appname: str, cfg: ModuleCfg, applications: dict, copy_manual_dir: bool, + copy_tests: bool, providers: dict, + suppliers: dict, ) -> None: if appname not in applications: - raise Exception(f'cannot find application dependency "{appname}" in application "{module_name}"') + raise Exception(f'cannot find application dependency "{appname}"') as_dir = applications[appname] applicationservice_file = join(as_dir, 'applicationservice.yml') if not isfile(applicationservice_file): @@ -133,6 +148,7 @@ def load_applicationservice(appname: str, install_dir, cfg, copy_manual_dir, + copy_tests, ) added.append(appname) with open(applicationservice_file) as yaml: @@ -142,6 +158,11 @@ def load_applicationservice(appname: str, providers.setdefault(provider, []) if appname not in providers[provider]: providers[provider].append(appname) + supplier = app.get('supplier') + if supplier: + suppliers.setdefault(supplier, []) + if appname not in suppliers[supplier]: + suppliers[supplier].append(appname) for xml in app.get('depends', []): if xml in added: continue @@ -151,17 +172,22 @@ def load_applicationservice(appname: str, cfg, applications, copy_manual_dir, + copy_tests, providers, + suppliers, ) -def load_image_informations(install_dir: str, +def load_image_informations(module_name: str, + install_dir: str, datas: dict, applications: dict, copy_manual_dir: bool, + copy_tests: bool, providers: dict, + suppliers: dict, ) -> ModuleCfg: - cfg = ModuleCfg() + cfg = ModuleCfg(module_name) added = [] for applicationservice in datas['applicationservices']: load_applicationservice(applicationservice, @@ -170,53 +196,35 @@ def load_image_informations(install_dir: str, cfg, applications, copy_manual_dir, + copy_tests, providers, + suppliers, ) return cfg -async def load_informations(server_name, datas, config): - await config.information.set('server_name', server_name) - if 'informations' not in datas: - return - for information, value in datas['informations'].items(): - await config.information.set(information, value) - if 'extra_domainnames' in datas['informations']: - for idx, extra_domainname in enumerate(datas['informations']['extra_domainnames']): - if extra_domainname in CONFIGS: - raise Exception(f'server "{server_name}" is duplicate') - value = list(CONFIGS[server_name]) - value[4] = idx + 1 - CONFIGS[extra_domainname] = tuple(value) - - -async def set_values(server_name, config, datas): - try: - if 'values' in datas: - for path, value in datas['values'].items(): - if isinstance(value, dict): - for idx, val in value.items(): - await config.option(path, int(idx)).value.set(val) - else: - await config.option(path).value.set(value) - except Exception as err: - await value_pprint(await config.value.dict(), config) - error_msg = f'cannot configure server "{server_name}": {err}' - raise Exception(error_msg) from err - #await config.value.dict() - - -async def valid_mandatories(server_name, config): +async def valid_mandatories(config): mandatories = await config.value.mandatory() if mandatories: - print() - print(f'=== Configuration: {server_name} ===') - await config.property.pop('mandatory') - await value_pprint(await config.value.dict(), config) - raise Exception(f'server "{server_name}" has mandatories variables without values "{", ".join(mandatories)}"') + server_name = None + for mandatory in mandatories: + path_server_name, path = mandatory.split('.', 1) + var_server_name = await config.option(path_server_name).option.description() + if server_name != var_server_name: + server_name = var_server_name + print() + print(f'=== Missing variables for {server_name} ===') + print(f' - {path}') + # await config.property.pop('mandatory') + # await value_pprint(await config.value.dict(), config) + exit(1) + #raise Exception('configuration has mandatories variables without values') -def load_config(copy_manual_dir=False, clean_directories=False): +def load_config(copy_manual_dir=False, + copy_tests=False, + clean_directories=False, + ): module_infos = {} applications = list_applications() with open('servers.json', 'r') as server_fh: @@ -225,74 +233,46 @@ def load_config(copy_manual_dir=False, clean_directories=False): modules = jsonfile['modules'] for module_name, datas in modules.items(): providers = {} + suppliers = {} install_dir = join(RISOTTO_CONFIG['directories']['dest'], module_name) if clean_directories: if isdir(install_dir): rmtree(install_dir) makedirs(install_dir) - module_infos[module_name] = {'infos': load_image_informations(install_dir, + module_infos[module_name] = {'infos': load_image_informations(module_name, + install_dir, datas, applications, copy_manual_dir, + copy_tests, providers, + suppliers, ), 'providers': providers, + 'suppliers': suppliers, 'install_dir': install_dir, } return module_infos - - - -async def load(display_name=None, - clean_directories=False, - copy_manual_dir=False, - hide_secret=False, - ): - # load images - module_infos = load_config(copy_manual_dir, clean_directories) - # load machines - ZONES_SERVER['providers'] = {} - for server_name, datas in SERVERS.items(): - if server_name in CONFIGS: - raise Exception(f'server "{server_name}" is duplicate') - module_info = module_infos[datas['module']] - CONFIGS[server_name] = await load_machine_config(server_name, - datas, - module_info, - display_name=display_name, - ) - if module_info['providers'] and 'informations' in datas and 'zones_name' in datas['informations']: - for zone_idx, zone_name in enumerate(datas['informations']['zones_name']): - if not zone_idx: - sname = server_name - else: - sname = datas['informations']['extra_domainnames'][zone_idx - 1] - ZONES_SERVER['providers'].setdefault(zone_name, {}) - for provider in module_info['providers']: - ZONES_SERVER['providers'][zone_name].setdefault(provider, []).append(sname) - # set servers.json values - for server_name, datas in SERVERS.items(): - config = CONFIGS[server_name]['config'] - await load_informations(server_name, datas, config) - await config.property.read_write() - if hide_secret: - try: - await config.option('general.hide_secret').value.set(True) - except AttributeError: - pass - await set_values(server_name, config, datas) - await config.property.read_only() - # force calculates all values (for linked values) - for server_name in SERVERS: - config = CONFIGS[server_name]['config'] - await config.property.pop('mandatory') - try: - await config.value.dict() - except Exception as err: - raise Exception(f'cannot display config for "{server_name}": {err}') - await config.property.add('mandatory') - # validate mandatories values - for server_name in SERVERS: - await valid_mandatories(server_name, CONFIGS[server_name]['config']) - - return module_infos +# +# +def load_module_config(module_name: str, + module_info: dict, + ): + cfg = RougailConfig.copy() + cfg['variable_namespace'] = ROUGAIL_NAMESPACE + cfg['variable_namespace_description'] = ROUGAIL_NAMESPACE_DESCRIPTION + if module_name == 'host': + #FIXME server_name == host ? + #FIXME cfg['tmpfile_dest_dir'] = datas['values'][f'{ROUGAIL_NAMESPACE}.host_install_dir'] + '/host/configurations/host' + cfg['default_systemd_directory'] = '/usr/local/lib/systemd' + cfg['templates_dir'] = module_info['infos'].templates_dir + cfg['dictionaries_dir'] = module_info['infos'].dictionaries_dir + cfg['functions_file'] = module_info['infos'].functions_file + cfg['multi_functions'] = MULTI_FUNCTIONS + cfg['extra_dictionaries'] = module_info['infos'].extra_dictionaries + cfg['extra_annotators'] = ['risotto.rougail'] + cfg['internal_functions'] = list(FUNCTIONS.keys()) + cfg['force_convert_dyn_option_description'] = True + cfg['module_name'] = module_name + #cfg['patches_dir'] = join(test_dir, 'patches') + return cfg diff --git a/src/risotto/machine.py b/src/risotto/machine.py index 3a28317..0b1110d 100644 --- a/src/risotto/machine.py +++ b/src/risotto/machine.py @@ -1,342 +1,31 @@ -from os import makedirs -from os.path import join, isdir -from warnings import warn_explicit -from typing import Any - -from tiramisu import Config -from tiramisu.error import ValueWarning +from .utils import SERVERS, SERVERS_JSON, MULTI_FUNCTIONS, load_domains +from .image import load_config, valid_mandatories # , load_modules_rougail_config from rougail import RougailConfig, RougailConvert -from .utils import MULTI_FUNCTIONS, CONFIGS, DOMAINS, value_pprint +from .rougail.annotator import calc_providers, calc_providers_global, calc_providers_dynamic, calc_providers_dynamic_follower, calc_providers_follower +from os import makedirs +# +from tiramisu import Config +from .utils import value_pprint from rougail.utils import normalize_family from rougail import RougailSystemdTemplate - - -ROUGAIL_NAMESPACE = 'general' -ROUGAIL_NAMESPACE_DESCRIPTION = 'Général' - - -async def set_linked_multi_variables(value: str, - linked_server: str=None, - variable_index: int=None, - linked_returns: str=None, - dynamic: str=None, - **kwargs: dict, - ) -> None: - if value is not None and linked_server is not None and 'linked_value_0' not in kwargs: - kwargs['linked_value_0'] = value - elif not linked_server: - linked_server = value - if not linked_server: +# +# +async def set_values(server_name, config, datas): + if 'values' not in datas: return - if linked_server not in CONFIGS: - warn_explicit(ValueWarning(f'cannot find linked server "{linked_server}"'), - ValueWarning, - __file__, - 3, - ) - return - config = CONFIGS[linked_server]['config'] - dico = {} - variables = {} - for key, kvalue in kwargs.items(): + server_path = normalize_family(server_name) + for vpath, value in datas['values'].items(): + path = f'{server_path}.{vpath}' try: - index = int(key.rsplit('_', 1)[-1]) - except ValueError: - raise Exception(f'unknown variable {key}') - if kvalue is None and not kwargs.get(f'allow_none_{index}', False): - return - if f'linked_value_{index}' not in kwargs: - # value is disabled - continue - if key.startswith('linked_provider_'): - path = await config.information.get('provider:' + kvalue, None) - if not path: - return - if index not in variables: - variables[index] = {'path': None, 'value': None, 'variable_index': False} - variables[index]['path'] = path - elif key.startswith('linked_value_'): - index = int(key[13]) - if index not in variables: - variables[index] = {'path': None, 'value': None, 'variable_index': False} - variables[index]['value'] = kvalue - elif key.startswith('variable_index_'): - index = int(key[15]) - if index not in variables: - variables[index] = {'path': None, 'value': None, 'variable_index': False} - variables[index]['variable_index'] = True - elif key.startswith('allow_none_'): - pass - else: - raise AttributeError(f'unknown parameter {key}') - await config.property.read_write() - if not isinstance(variables[0]['value'], list): - variables[0]['value'] = [variables[0]['value']] - if dynamic: - dynamic = normalize_family(dynamic) - _dynamic = None - try: - if variables[0]['value']: - for first_idx, first_value in enumerate(variables[0]['value']): - slave_idxes = [] - if dynamic: - _dynamic = dynamic - else: - _dynamic = normalize_family(first_value) - for index in sorted(list(variables)): - path = variables[index]['path'] - if '{suffix}' in path: - path = path.replace('{suffix}', _dynamic) - elif first_idx != 0: - continue - vvalue = variables[index]['value'] - option = config.forcepermissive.option(path) - if not await option.option.isfollower(): - #print('===>', path, vvalue, await option.option.ismulti(), await option.option.issubmulti()) - multi = await option.option.ismulti() - if multi: - isleader = await option.option.isleader() - if not isinstance(vvalue, list): - vvalue = [vvalue] - # elif isleader: - # raise Exception('leader must not be a multi from now ...') - values = await option.value.get() - for val in vvalue: - if val not in values: - if isleader: - slave_idxes.append(len(values)) - values.append(val) - elif isleader: - slave_idxes.append(values.index(val)) - await option.value.set(values) - await option.owner.set('link') - else: - if isinstance(vvalue, list) and len(vvalue) == 1: - vvalue = vvalue[0] - await option.value.set(vvalue) - await option.owner.set('link') - else: - # print('===<', path, vvalue, await option.option.ismulti(), await option.option.issubmulti()) - if not slave_idxes: - raise Exception('please declare the leader variable before the follower') - if variables[index]['variable_index']: - vvalue = vvalue[variable_index] - if not isinstance(vvalue, list): - vvalue = [vvalue] * len(slave_idxes) - # if isinstance(vvalue, list) and not await option.option.issubmulti(): - for idx, val in enumerate(vvalue): - option = config.forcepermissive.option(path, slave_idxes[idx]) - await option.value.set(val) - await option.owner.set('link') - except Exception as err: - await config.property.read_only() - raise err from err - await config.property.read_only() - if not dynamic: - dynamic = _dynamic - - - if linked_returns is not None: - linked_variable = await config.information.get('provider:' + linked_returns, None) - if not linked_variable: - warn_explicit(ValueWarning(f'cannot find linked variable "{linked_returns}" in linked server "{linked_server}"'), - ValueWarning, - __file__, - 0, - ) - return - if dynamic: - linked_variable = linked_variable.replace('{suffix}', normalize_family(dynamic)) - elif '{suffix}' in linked_variable: - idx = CONFIGS[linked_server]['interface_index'] - linked_variable = linked_variable.replace('{suffix}', str(idx)) - ret = await config.forcepermissive.option(linked_variable).value.get() - else: - ret = get_ip_from_domain(linked_server) - return ret - - -async def set_linked(linked_server: str, - linked_provider: str, - linked_value: str, - linked_returns: str=None, - dynamic: str=None, - ): - if None in (linked_server, linked_provider, linked_value): - return - if linked_server not in CONFIGS: - warn_explicit(ValueWarning(f'cannot find linked server "{linked_server}"'), - ValueWarning, - __file__, - 0, - ) - return - config = CONFIGS[linked_server]['config'] - path = await config.information.get('provider:' + linked_provider, None) - if not path: - warn_explicit(ValueWarning(f'cannot find provider "{linked_provider}" in linked server "{linked_server}"'), - ValueWarning, - __file__, - 0, - ) - return - await config.property.read_write() - try: - option = config.forcepermissive.option(path) - if await option.option.ismulti(): - values = await option.value.get() - if linked_value not in values: - values.append(linked_value) - await option.value.set(values) - await option.owner.set('link') - else: - await option.value.set(linked_value) - await option.owner.set('link') - except Exception as err: - await config.property.read_only() - raise err from err - await config.property.read_only() - if linked_returns is not None: - linked_variable = await config.information.get('provider:' + linked_returns, None) - if not linked_variable: - warn_explicit(ValueWarning(f'cannot find linked variable "{linked_returns}" in linked server "{linked_server}"'), - ValueWarning, - __file__, - 0, - ) - return - else: - linked_variable = None - if linked_variable is not None: - if dynamic: - linked_variable = linked_variable.replace('{suffix}', normalize_family(dynamic)) - elif '{suffix}' in linked_variable: - idx = CONFIGS[linked_server]['interface_index'] - linked_variable = linked_variable.replace('{suffix}', str(idx)) - ret = await config.forcepermissive.option(linked_variable).value.get() - else: - ret = normalize_family(linked_value) - return ret - - -async def get_linked_configuration(linked_server: str, - linked_provider: str, - dynamic: str=None, - ): - if linked_server not in CONFIGS: - warn_explicit(ValueWarning(f'cannot find linked server "{linked_server}"'), - ValueWarning, - __file__, - 1, - ) - return - config = CONFIGS[linked_server]['config'] - path = await config.information.get('provider:' + linked_provider, None) - if not path: - warn_explicit(ValueWarning(f'cannot find variable "{path}" in linked server "{linked_server}"'), - ValueWarning, - __file__, - 1, - ) - return - if dynamic: - path = path.replace('{suffix}', normalize_family(dynamic)) - try: - return await config.forcepermissive.option(path).value.get() - except AttributeError as err: - warn_explicit(ValueWarning(f'cannot find get value of "{path}" in linked server "{linked_server}": {err}'), - ValueWarning, - __file__, - 1, - ) - - -class Empty: - pass -empty = Empty() - - -async def set_linked_configuration(_linked_value: Any, - linked_server: str, - linked_provider: str, - linked_value: Any=empty, - dynamic: str=None, - leader_provider: str=None, - leader_value: Any=None, - leader_index: int=None, - ): - if linked_value is not empty: - _linked_value = linked_value - linked_value = _linked_value - if linked_server is None: - return - if linked_value is None or linked_server not in CONFIGS: - warn_explicit(ValueWarning(f'cannot find linked server "{linked_server}"'), - ValueWarning, - __file__, - 2, - ) - return - config = CONFIGS[linked_server]['config'] - path = await config.information.get('provider:' + linked_provider, None) - if not path: - warn_explicit(ValueWarning(f'cannot find variable "{path}" in linked server "{linked_server}"'), - ValueWarning, - __file__, - 2, - ) - return - if dynamic: - path = path.replace('{suffix}', normalize_family(dynamic)) - await config.property.read_write() - try: - if leader_provider is not None: - leader_path = await config.information.get('provider:' + leader_provider, None) - if not leader_path: - await config.property.read_only() - warn_explicit(ValueWarning(f'cannot find leader variable with leader_provider "{leader_provider}" in linked server "{linked_server}"'), - ValueWarning, - __file__, - 2, - ) - return - if dynamic: - leader_path = leader_path.replace('{suffix}', normalize_family(dynamic)) - values = await config.forcepermissive.option(leader_path).value.get() - if not isinstance(leader_value, list): - leader_value = [leader_value] - for lv in leader_value: - if lv in values: - slave_idx = values.index(lv) - slave_option = config.forcepermissive.option(path, slave_idx) - if await slave_option.option.issubmulti(): - slave_values = await slave_option.value.get() - if linked_value not in slave_values: - slave_values.append(linked_value) - await slave_option.value.set(slave_values) - await slave_option.owner.set('link') - else: - await slave_option.value.set(linked_value) - await slave_option.owner.set('link') - else: - option = config.forcepermissive.option(path, leader_index) - if leader_index is None and await option.option.ismulti() and not isinstance(linked_value, list): - values = await option.value.get() - if linked_value not in values: - values.append(linked_value) - await option.value.set(values) - await option.owner.set('link') + if isinstance(value, dict): + for idx, val in value.items(): + await config.option(path, int(idx)).value.set(val) else: - await option.value.set(linked_value) - await option.owner.set('link') - except AttributeError as err: - if linked_provider == 'oauth2_external': - raise ValueError(str(err)) from err - pass - except Exception as err: - await config.property.read_only() - raise err from err - await config.property.read_only() + await config.option(path).value.set(value) + except Exception as err: + await value_pprint(await config.value.dict(), config) + error_msg = f'cannot configure variable {vpath} for server "{server_name}": {err}' + raise Exception(error_msg) from err def get_ip_from_domain(domain): @@ -344,59 +33,18 @@ def get_ip_from_domain(domain): return hostname, domainname = domain.split('.', 1) return DOMAINS[domainname][1][DOMAINS[domainname][0].index(hostname)] + return optiondescription['option_0'] -async def load_machine_config(server_name: str, - datas: dict, - module_info: dict, - display_name, - ): - optiondescription = {'set_linked': set_linked, - 'set_linked_multi_variables': set_linked_multi_variables, - 'get_linked_configuration': get_linked_configuration, - 'set_linked_configuration': set_linked_configuration, - 'get_ip_from_domain': get_ip_from_domain, - } - cfg = RougailConfig.copy() - module_info['infos'].servers.append(server_name) - cfg['variable_namespace'] = ROUGAIL_NAMESPACE - cfg['variable_namespace_description'] = ROUGAIL_NAMESPACE_DESCRIPTION - if datas['module'] == 'host': - cfg['tmpfile_dest_dir'] = datas['values'][f'{ROUGAIL_NAMESPACE}.host_install_dir'] + '/host/configurations/' + server_name - cfg['templates_dir'] = module_info['infos'].templates_dir - cfg['dictionaries_dir'] = module_info['infos'].dictionaries_dir - cfg['functions_file'] = module_info['infos'].functions_file - cfg['multi_functions'] = MULTI_FUNCTIONS - cfg['extra_dictionaries'] = module_info['infos'].extra_dictionaries - cfg['extra_annotators'].append('risotto.rougail') - cfg['internal_functions'] = list(optiondescription.keys()) - try: - eolobj = RougailConvert(cfg) - except Exception as err: - print(f"Try to load {module_info['infos'].modules}") - raise err from err - tiram_obj = eolobj.save(None) -# if server_name == 'revprox.in.silique.fr': -# print(tiram_obj) - #cfg['patches_dir'] = join(test_dir, 'patches') - try: - exec(tiram_obj, None, optiondescription) - except Exception as err: - print(tiram_obj) - raise Exception(f'unknown error when load tiramisu object {err}') from err - config = await Config(optiondescription['option_0'], display_name=display_name) - await config.property.read_write() - try: - add_srv = await config.option('machine.add_srv').value.get() - except AttributeError: - add_srv = False - return {'config': config, - 'templates_informations': cfg, - 'interface_index': 0, - 'module_name': datas['module'], - 'add_srv': add_srv, - 'providers': module_info['providers'], - } +ROUGAIL_NAMESPACE = 'general' +ROUGAIL_NAMESPACE_DESCRIPTION = 'Général' +FUNCTIONS = {'get_ip_from_domain': get_ip_from_domain, + 'calc_providers': calc_providers, + 'calc_providers_global': calc_providers_global, + 'calc_providers_dynamic': calc_providers_dynamic, + 'calc_providers_dynamic_follower': calc_providers_dynamic_follower, + 'calc_providers_follower': calc_providers_follower, + } async def templates(server_name, @@ -404,24 +52,100 @@ async def templates(server_name, templates_informations, srv=False, just_copy=False, - **kwargs, ): - values = await config.value.dict() engine = RougailSystemdTemplate(config, templates_informations) if just_copy: + # for all engine to none + ori_engines = {} for eng in engine.engines: - if eng != 'none': - engine.engines[eng] = engine.engines['none'] -# if server_name == 'dovecot.in.silique.fr': -# print() -# print(f'=== Configuration: {server_name} ===') -# pprint(values) + if eng == 'none': + continue + ori_engines[eng] = engine.engines[eng] + engine.engines[eng] = engine.engines['none'] try: await engine.instance_files() except Exception as err: print() print(f'=== Configuration: {server_name} ===') + values = await config.value.dict() await value_pprint(values, config) - raise err from err + print(err) + print(await config.option('general.nginx.nginx_default_http').value.get()) + exit(1) + #raise err from err + if just_copy: + for eng, old_engine in ori_engines.items(): + engine.engines[eng] = old_engine if srv: makedirs(srv) + + + +async def load(display_name=None, + clean_directories=False, + copy_manual_dir=False, + copy_tests=False, + hide_secret=False, + ): + #load_zones() +# # load images + #FIXME useful + module_infos = load_config(copy_manual_dir, + copy_tests, + clean_directories, + ) +# modules_rougail_config = load_modules_rougail_config(module_infos) + cfg = RougailConfig.copy() + cfg['variable_namespace'] = ROUGAIL_NAMESPACE + cfg['variable_namespace_description'] = ROUGAIL_NAMESPACE_DESCRIPTION + cfg['multi_functions'] = MULTI_FUNCTIONS + cfg['extra_annotators'] = ['risotto.rougail'] + cfg['internal_functions'] = list(FUNCTIONS.keys()) + cfg['force_convert_dyn_option_description'] = True +# cfg['module_name'] = module_name + functions_files = set() + load_domains() + for server_name, datas in SERVERS.items(): + module_info = module_infos[datas['module']] + functions_files |= set(module_info['infos'].functions_file) + cfg['functions_file'] = list(functions_files) + eolobj = RougailConvert(cfg) + cfg['risotto_globals'] = {} + for server_name, datas in SERVERS.items(): + module_info = module_infos[datas['module']] + cfg['dictionaries_dir'] = module_info['infos'].dictionaries_dir + cfg['extra_dictionaries'] = module_info['infos'].extra_dictionaries + informations = SERVERS_JSON['servers'][server_name].get('informations') + if informations: + cfg['risotto_globals'][server_name] = {'global:server_name': server_name, + 'global:zones_name': informations['zones_name'], + 'global:zones_list': list(range(len(informations['zones_name']))), + } + values = [] + for s_idx in cfg['risotto_globals'][server_name]['global:zones_list']: + if not s_idx: + values.append(server_name) + else: + values.append(informations['extra_domainnames'][s_idx - 1]) + cfg['risotto_globals'][server_name]['global:server_names'] = values + else: + cfg['risotto_globals'][server_name] = {'global:server_name': server_name} + eolobj.load_dictionaries(path_prefix=server_name) + tiram_obj = eolobj.save(None) + optiondescription = FUNCTIONS.copy() + try: + exec(tiram_obj, None, optiondescription) + except Exception as err: + print(tiram_obj) + raise Exception(f'unknown error when load tiramisu object {err}') from err + config = await Config(optiondescription['option_0'], + display_name=display_name, + ) + await config.property.pop('validator') + await config.property.pop('cache') + for server_name, datas in SERVERS.items(): + await set_values(server_name, config, datas) + await config.property.read_only() + await config.property.add('cache') + await valid_mandatories(config) + return module_infos, cfg, config diff --git a/src/risotto/rougail/annotator.py b/src/risotto/rougail/annotator.py index 15aa063..8fbe032 100644 --- a/src/risotto/rougail/annotator.py +++ b/src/risotto/rougail/annotator.py @@ -1,5 +1,122 @@ from rougail.annotator.variable import Walk -from risotto.utils import _ +from risotto.utils import _, multi_function +from warnings import warn + + + +def _parse_kwargs(provider, dns, kwargs, index=None): + values = {} + for key, value in kwargs.items(): + if '_' not in key: + raise Exception(f'unknown attribute {key} in calc_providers_global with provider {provider}') + k, idx = key.rsplit('_', 1) + values.setdefault(idx, {})[k] = value + for idx, data in values.items(): + if index is not None and int(idx) != index: + continue + if 'dns' not in data or (isinstance(data['dns'], list) and dns not in data['dns']) or (not isinstance(data['dns'], list) and data['dns'] != dns): + continue + del data['dns'] + yield data + + +@multi_function +def calc_providers_global(provider, multi, value, suffix=None): + if suffix is not None: + return value[int(suffix)] + return value + + +@multi_function +def calc_providers_follower(provider, multi, dns, leader, index, **kwargs): + ret = [] + for data in _parse_kwargs(provider, dns, kwargs): + if 'value' not in data: + continue + if 'leader' in data: + if isinstance(data['leader'], list): + for idx, leader_iter in enumerate(data['leader']): + if leader_iter == leader: + ret.append(data['value'][idx]) + elif data['leader']== leader: + ret.extend(data['value']) + else: + if isinstance(data['value'], list): + for v in data['value']: + if v not in ret: + ret.append(v) + elif data['value'] not in ret: + ret.append(data['value']) + if multi: + return ret + if ret: + return ret[0] + + +@multi_function +def calc_providers_dynamic_follower(provider, multi, dns, leader, index, suffix, **kwargs): + ret = [] + for data in _parse_kwargs(provider, dns, kwargs): + if 'value' not in data: + continue + if data['dynamic'] != suffix: + continue + if 'leader' in data: + for idx, leader_iter in enumerate(data['leader']): + if leader_iter == leader: + if isinstance(data['value'], list): + ret.append(data['value'][idx]) + else: + ret.append(data['value']) + else: + if isinstance(data['value'], list): + for v in data['value']: + if v not in ret: + ret.append(v) + elif data['value'] not in ret: + ret.append(data['value']) + if multi: + return ret + if ret: + return ret[0] + + +@multi_function +def calc_providers_dynamic(provider, multi, dns, suffix, **kwargs): + ret = [] + for data in _parse_kwargs(provider, dns, kwargs): + if 'value' not in data: + continue + if data['dynamic'] != suffix: + continue + if isinstance(data['value'], list): + for v in data['value']: + if v not in ret: + ret.append(v) + elif data['value'] not in ret: + ret.append(data['value']) + if multi: + return ret + if ret: + return ret[0] + + +@multi_function +def calc_providers(provider, multi, dns, suffix=None, **kwargs): + ret = [] + for data in _parse_kwargs(provider, dns, kwargs): + if isinstance(data['value'], list): + for v in data['value']: + if v in ret: + continue + ret.append(v) + + elif data['value'] not in ret: + ret.append(data['value']) + if multi: + return ret + if ret: + return ret[0] class Annotator(Walk): @@ -10,48 +127,217 @@ class Annotator(Walk): self.objectspace = objectspace # self.convert_get_linked_information() # self.convert_provider() + self.set_suppliers() + self.convert_providers() + self.convert_suppliers() - def convert_get_linked_information(self): - if not hasattr(self.objectspace.space, 'constraints') or \ - not hasattr(self.objectspace.space.constraints, 'fill'): - return - for fill in self.objectspace.space.constraints.fill: - if fill.name == 'get_linked_configuration': - # add server_name - param = self.objectspace.param(fill.xmlfiles) - param.name = 'server_name' - param.type = 'information' - param.text = 'server_name' - fill.param.append(param) - # add current_user - param = self.objectspace.param(fill.xmlfiles) - param.name = 'current_user' - param.type = 'information' - param.text = 'current_user' - fill.param.append(param) - # add test - param = self.objectspace.param(fill.xmlfiles) - param.name = 'test' - param.type = 'target_information' - param.text = 'test' - fill.param.append(param) - - def convert_provider(self): - if not hasattr(self.objectspace.space, 'variables'): - return - for family in self.get_families(): - if not hasattr(family, 'provider'): + def set_suppliers(self) -> dict: + """ get supplier informations + return something like: + {'Host': ['host1.example.net', 'host2.example.net']} + """ + self.suppliers = {} + for variable in self.get_variables(): + if not hasattr(variable, 'supplier') or ':' in variable.supplier: continue - if 'dynamic' not in vars(family): - raise Exception(_(f'{family.name} is not a dynamic family so cannot have provider attribute')) - if not hasattr(family, 'information'): - family.information = self.objectspace.information(family.xmlfiles) - family.information.provider = family.provider - del family.provider + nf_dns = variable.path.split('.', 1)[0] + server_name = self.objectspace.space.variables[nf_dns].doc + self.suppliers.setdefault(variable.supplier, []).append({'option': variable, 'dns': server_name, 'path_prefix': nf_dns, 'zones': set(self.objectspace.rougailconfig['risotto_globals'][server_name]['global:zones_name'])}) + + def convert_suppliers(self): + for supplier, data in self.suppliers.items(): + if supplier == 'Host': + continue + for s_dico in data: + if supplier not in self.providers: + continue + for p_dico in self.providers[supplier]: + if s_dico['zones'] & p_dico['zones']: + s_dico['option'].value = p_dico['dns'] + new_value = self.objectspace.value(None) + new_value.name = p_dico['dns'] + s_dico['option'].value = [new_value] + break + + + def convert_providers(self): + self.providers = {} for variable in self.get_variables(): if not hasattr(variable, 'provider'): continue - if not hasattr(variable, 'information'): - variable.information = self.objectspace.information(variable.xmlfiles) - variable.information.provider = variable.provider - del variable.provider + nf_dns = variable.path.split('.', 1)[0] + server_name = self.objectspace.space.variables[nf_dns].doc + provider_name = variable.provider + if ':' in provider_name: + key_name, key_type = provider_name.rsplit(':', 1) + is_provider = False + else: + key_name = key_type = provider_name + is_provider = True + if provider_name != 'Host': + self.providers.setdefault(provider_name, []).append({'option': variable, 'dns': server_name, 'path_prefix': nf_dns, 'zones': set(self.objectspace.rougailconfig['risotto_globals'][server_name]['global:zones_name'])}) + if key_name != 'global' and key_name not in self.suppliers: + #warn(f'cannot find supplier "{key_name}" for "{server_name}"') + continue + # create a fill for this variable + fill = self.objectspace.fill(variable.xmlfiles) + new_target = self.objectspace.target(variable.xmlfiles) + new_target.name = variable + fill.target = [new_target] + if key_name == 'global': + fill.name = 'calc_providers_global' + elif self.objectspace.paths.is_dynamic(variable): + if self.objectspace.paths.is_follower(variable): + fill.name = 'calc_providers_dynamic_follower' + else: + fill.name = 'calc_providers_dynamic' + elif self.objectspace.paths.is_follower(variable): + fill.name = 'calc_providers_follower' + else: + fill.name = 'calc_providers' + fill.namespace = variable.namespace + fill.index = 0 + # first parameter: the provider name (something link Host:incoming_ports) + param = self.objectspace.param(variable.xmlfiles) + param.name = 'provider' + param.text = provider_name + fill.param = [param] + # second parameter: current variable is a multi variable? + param = self.objectspace.param(variable.xmlfiles) + param.name = 'multi' + param.text = variable.multi + param.type = 'boolean' + fill.param.append(param) + if self.objectspace.paths.is_follower(variable): + param = self.objectspace.param(variable.xmlfiles) + param.name = 'leader' + param.text = self.objectspace.paths.get_leader(variable) + param.propertyerror = False + param.type = 'variable' + fill.param.append(param) + try: + leader_provider = self.objectspace.paths.get_leader(variable).provider + except: + leader_provider = None + # + param = self.objectspace.param(variable.xmlfiles) + param.name = 'index' + param.type = 'index' + fill.param.append(param) + if self.objectspace.paths.is_dynamic(variable): + # if dynamic: current suffix + # and add current DNS name, this is useful to known if supplier is link to this provider + param = self.objectspace.param(variable.xmlfiles) + param.name = 'suffix' + param.type = 'suffix' + fill.param.append(param) + if key_name != 'global': + param = self.objectspace.param(variable.xmlfiles) + param.name = 'dns' + param.text = server_name + fill.param.append(param) + if key_name == 'global': + param = self.objectspace.param(variable.xmlfiles) + param.text = self.objectspace.rougailconfig['risotto_globals'][server_name][provider_name] + param.name = 'value' + fill.param.append(param) + else: + # parse all supplier link to current provider + for idx, data in enumerate(self.suppliers[key_name]): + option = data['option'] + dns = data['dns'] + # if not provider, get the true option that we want has value + if not is_provider: + path_prefix = data['path_prefix'] + try: + supplier_option = self.objectspace.paths.get_supplier(f'supplier:{provider_name}', path_prefix) + except KeyError: + #warn(f'cannot find supplier "{provider_name}" for "{dns}"') + continue + # first of all, get the supplier name + param = self.objectspace.param(variable.xmlfiles) + param.name = f'dns_{idx}' + param.text = option + param.propertyerror = False + param.type = 'variable' + fill.param.append(param) + if not is_provider and \ + self.objectspace.paths.is_follower(variable): + param = self.objectspace.param(variable.xmlfiles) + param.name = f'leader_{idx}' + if fill.name == 'calc_providers_follower': + param.text = dns + else: + if self.objectspace.paths.is_follower(supplier_option): + param.text = self.objectspace.paths.get_leader(supplier_option) + else: + param.text = self.objectspace.paths.get_supplier(f'supplier:{leader_provider}', path_prefix) + param.propertyerror = False + param.type = 'variable' + fill.param.append(param) + # get the current DNS name for dynamic variable + if self.objectspace.paths.is_dynamic(variable): + param = self.objectspace.param(variable.xmlfiles) + param.name = f'dynamic_{idx}' + param.text = dns + fill.param.append(param) + # get the current value! + param = self.objectspace.param(variable.xmlfiles) + param.name = f'value_{idx}' + if is_provider: + param.text = dns + else: + param.text = supplier_option + param.propertyerror = False + param.type = 'variable' + fill.param.append(param) + if not hasattr(self.objectspace.space.variables[nf_dns], 'constraints'): + self.objectspace.space.variables[nf_dns].constraints = self.objectspace.constraints(None) + if not hasattr(self.objectspace.space.variables[nf_dns].constraints, 'fill'): + self.objectspace.space.variables[nf_dns].constraints.fill = [] + self.objectspace.space.variables[nf_dns].constraints.fill.append(fill) + +# def convert_get_linked_information(self): +# if not hasattr(self.objectspace.space, 'constraints') or \ +# not hasattr(self.objectspace.space.constraints, 'fill'): +# return +# for fill in self.objectspace.space.constraints.fill: +# if fill.name == 'get_linked_configuration': +# # add server_name +# param = self.objectspace.param(fill.xmlfiles) +# param.name = 'server_name' +# param.type = 'information' +# param.text = 'server_name' +# fill.param.append(param) +# # add current_user +# param = self.objectspace.param(fill.xmlfiles) +# param.name = 'current_user' +# param.type = 'information' +# param.text = 'current_user' +# fill.param.append(param) +# # add test +# param = self.objectspace.param(fill.xmlfiles) +# param.name = 'test' +# param.type = 'target_information' +# param.text = 'test' +# fill.param.append(param) +# +# def convert_provider(self): +# if not hasattr(self.objectspace.space, 'variables'): +# return +# for family in self.get_families(): +# if not hasattr(family, 'provider'): +# continue +# if 'dynamic' not in vars(family): +# raise Exception(_(f'{family.name} is not a dynamic family so cannot have provider attribute')) +# if not hasattr(family, 'information'): +# family.information = self.objectspace.information(family.xmlfiles) +# family.information.provider = family.provider +# del family.provider +# for variable in self.get_variables(): +# if not hasattr(variable, 'provider'): +# continue +# if not hasattr(variable, 'information'): +# variable.information = self.objectspace.information(variable.xmlfiles) +# variable.information.provider = variable.provider +# del variable.provider diff --git a/src/risotto/utils.py b/src/risotto/utils.py index 6d80026..2eed988 100644 --- a/src/risotto/utils.py +++ b/src/risotto/utils.py @@ -6,12 +6,13 @@ from toml import load as toml_load from pprint import pprint +SETTINGS = {'config': None} MULTI_FUNCTIONS = [] -CONFIGS = {} DOMAINS = {} ZONES = {} -ZONES_SERVER = {} +SERVERS_JSON = {} SERVERS = {} +CONFIGS = {} with open(environ.get('CONFIG_FILE', 'risotto.conf'), 'r') as fh: @@ -40,10 +41,10 @@ async def value_pprint(dico, config): def load_zones_server(): - if 'zones' in ZONES_SERVER: + if 'zones' in SERVERS_JSON: return with open('servers.json', 'r') as server_fh: - ZONES_SERVER.update(load(server_fh)) + SERVERS_JSON.update(load(server_fh)) def load_zones(): @@ -52,8 +53,8 @@ def load_zones(): return load_zones_server() - ZONES.update(ZONES_SERVER['zones']) - for server_name, server in ZONES_SERVER['servers'].items(): + ZONES.update(SERVERS_JSON['zones']) + for server_name, server in SERVERS_JSON['servers'].items(): if 'informations' not in server: continue server_zones = server['informations']['zones_name'] @@ -78,7 +79,7 @@ def load_domains(): if DOMAINS: return load_zones() - for zone_name, zone in ZONES_SERVER['zones'].items(): + for zone_name, zone in SERVERS_JSON['zones'].items(): if 'domain_name' in zone: hosts = [] ips = [] @@ -103,5 +104,4 @@ def _get_ip(server_name: str, if server_name not in zone['hosts']: raise ValueError(f"cannot set IP in unknown server '{server_name}'") server_index = zone['hosts'].index(server_name) -# print(server_name, zones_name, index, str(ip_address(zone['start_ip']) + server_index)) return str(ip_address(zone['start_ip']) + server_index) diff --git a/src/risotto/x509.py b/src/risotto/x509.py index 54731c1..4311c7b 100644 --- a/src/risotto/x509.py +++ b/src/risotto/x509.py @@ -155,7 +155,7 @@ def gen_cert_iter(cn, raise Exception(f'cannot find CA file "{cert_ca_name}"') if not isfile(cert_name): if not isfile(key_ca_name): - raise Exception(f"cannot find CA private key (\"{authority_cn}\") to sign certificat for \"{cn}\", is it a Let's Encrypt certification?") + raise Exception(f"cannot find CA private key (\"{authority_cn}\") to sign certificat for \"{cn}\" ({key_ca_name}), is it a Let's Encrypt certification?") if not isdir(dir_name): makedirs(dir_name) if isfile(sn_name):