diff --git a/src/risotto/controller.py b/src/risotto/controller.py index 0c4caa2..bb4313b 100644 --- a/src/risotto/controller.py +++ b/src/risotto/controller.py @@ -1,6 +1,22 @@ +from os import listdir, makedirs +from os.path import join, isdir, isfile +from shutil import rmtree +from traceback import print_exc +from typing import Dict +from rougail import RougailConvert, RougailConfig, RougailUpgrade +try: + from tiramisu3 import Storage, Config +except: + from tiramisu import Storage, Config + +from .config import get_config +from .utils import _, tiramisu_display_name +from .logger import log from .dispatcher import dispatcher from .context import Context -from .utils import _ + + +RougailConfig['variable_namespace'] = 'configuration' class Controller: @@ -8,7 +24,7 @@ class Controller: """ def __init__(self, test: bool, - ): + ) -> None: pass async def call(self, @@ -77,3 +93,239 @@ class Controller: risotto_context, ): pass + + +class TiramisuController(Controller): + def __init__(self, + test: bool, + ) -> None: + if not 'dataset_name' in vars(self): + raise Exception(f'please specify "dataset_name" to "{self.__class__.__name__}"') + self.tiramisu_cache_root_path = join(get_config()['cache']['root_path'], self.dataset_name) + if not test: + db_conf = get_config()['database']['tiramisu_dsn'] + self.save_storage = Storage(engine='postgres') + self.save_storage.setting(dsn=db_conf) + if self.dataset_name != 'servermodel': + self.optiondescription = None + dispatcher.set_function('v1.setting.dataset.updated', + None, + TiramisuController.dataset_updated, + self.__class__.__module__, + ) + + async def on_join(self, + risotto_context: Context, + ) -> None: + if isdir(self.tiramisu_cache_root_path): + await self.load_datas(risotto_context) + + async def dataset_updated(self, + risotto_context: Context, + ) -> Dict: + await self.gen_dictionaries(risotto_context) + await self.load_datas(risotto_context) + + async def gen_dictionaries(self, + risotto_context: Context, + ) -> None: + sources = await self.get_sources(risotto_context) + self._aggregate_tiramisu_funcs(sources) + self._convert_dictionaries_to_tiramisu(sources) + + async def get_sources(self, + risotto_context: Context, + ) -> None: + return await self.call('v1.setting.source.list', + risotto_context, + ) + + def _aggregate_tiramisu_funcs(self, + sources: list, + ) -> None: + dest_file = join(self.tiramisu_cache_root_path, 'funcs.py') + if not isdir(self.tiramisu_cache_root_path): + makedirs(self.tiramisu_cache_root_path) + with open(dest_file, 'wb') as funcs: + funcs.write(b"""try: + from tiramisu3 import valid_network_netmask, valid_ip_netmask, valid_broadcast, valid_in_network, valid_not_equal as valid_differ, valid_not_equal, calc_value +except: + from tiramisu import valid_network_netmask, valid_ip_netmask, valid_broadcast, valid_in_network, valid_not_equal as valid_differ, valid_not_equal, calc_value + +""") + for source in sources: + root_path = join(source['source_directory'], + self.dataset_name, + ) + if not isdir(root_path): + continue + for service in listdir(root_path): + path = join(root_path, + service, + 'funcs', + ) + if not isdir(path): + continue + for filename in listdir(path): + if not filename.endswith('.py'): + continue + filename_path = join(path, filename) + with open(filename_path, 'rb') as fh: + funcs.write(f'# {filename_path}\n'.encode()) + funcs.write(fh.read()) + funcs.write(b'\n') + + def _convert_dictionaries_to_tiramisu(self, sources: list) -> None: + funcs_file = join(self.tiramisu_cache_root_path, 'funcs.py') + tiramisu_file = join(self.tiramisu_cache_root_path, 'tiramisu.py') + dictionaries_dir = join(self.tiramisu_cache_root_path, 'dictionaries') + extras_dictionaries_dir = join(self.tiramisu_cache_root_path, 'extra_dictionaries') + if isdir(dictionaries_dir): + rmtree(dictionaries_dir) + makedirs(dictionaries_dir) + if isdir(extras_dictionaries_dir): + rmtree(extras_dictionaries_dir) + makedirs(extras_dictionaries_dir) + extras = [] + upgrade = RougailUpgrade() + for source in sources: + root_path = join(source['source_directory'], + self.dataset_name, + ) + if not isdir(root_path): + continue + for service in listdir(root_path): + # upgrade dictionaries + path = join(root_path, + service, + 'dictionaries', + ) + if not isdir(path): + continue + upgrade.load_xml_from_folders(path, + dictionaries_dir, + RougailConfig['variable_namespace'], + ) + for service in listdir(root_path): + # upgrade extra dictionaries + path = join(root_path, + service, + 'extras', + ) + if not isdir(path): + continue + for namespace in listdir(path): + extra_dir = join(path, + namespace, + ) + if not isdir(extra_dir): + continue + extra_dictionaries_dir = join(extras_dictionaries_dir, + namespace, + ) + if not isdir(extra_dictionaries_dir): + makedirs(extra_dictionaries_dir) + extras.append((namespace, [extra_dictionaries_dir])) + upgrade.load_xml_from_folders(extra_dir, + extra_dictionaries_dir, + namespace, + ) + del upgrade + config = RougailConfig.copy() + config['functions_file'] = funcs_file + config['dictionaries_dir'] = [dictionaries_dir] + config['extra_dictionaries'] = {} + for extra in extras: + config['extra_dictionaries'][extra[0]] = extra[1] + eolobj = RougailConvert(rougailconfig=config) + eolobj.save(tiramisu_file) + + async def load(self, + risotto_context: Context, + name: str, + to_deploy: bool=False, + ) -> Config: + if self.optiondescription is None: + # use file in cache + tiramisu_file = join(self.tiramisu_cache_root_path, 'tiramisu.py') + if not isfile(tiramisu_file): + raise Exception(_(f'unable to load the "{self.dataset_name}" configuration, is dataset loaded?')) + with open(tiramisu_file) as fileio: + tiramisu_locals = {} + try: + exec(fileio.read(), None, tiramisu_locals) + except Exception as err: + raise Exception(_(f'unable to load tiramisu file {tiramisu_file}: {err}')) + + self.optiondescription = tiramisu_locals['option_0'] + del tiramisu_locals + try: + letter = self.dataset_name[0] + if not to_deploy: + session_id = f'{letter}_{name}' + else: + session_id = f'{letter}td_{name}' + config = await Config(self.optiondescription, + session_id=session_id, + storage=self.save_storage, + display_name=tiramisu_display_name, + ) + # change default rights + await config.property.read_only() + await config.permissive.add('basic') + await config.permissive.add('normal') + await config.permissive.add('expert') + + # set information and owner + await config.owner.set(session_id) + await config.information.set(f'{self.dataset_name}_name', name) + except Exception as err: + if get_config()['global']['debug']: + print_exc() + msg = _(f'unable to load config for {self.dataset_name} "{name}": {err}') + await log.error_msg(risotto_context, + None, + msg, + ) + return config + + async def _deploy_configuration(self, + dico: dict, + ) -> None: + config_std = dico['config_to_deploy'] + config = dico['config'] + # when deploy, calculate force_store_value + ro = await config_std.property.getdefault('read_only', 'append') + if 'force_store_value' not in ro: + await config_std.property.read_write() + if self.dataset_name == 'servermodel': + # server_deployed should be hidden + await config_std.forcepermissive.option('configuration.general.server_deployed').value.set(True) + ro = frozenset(list(ro) + ['force_store_value']) + rw = await config_std.property.getdefault('read_write', 'append') + rw = frozenset(list(rw) + ['force_store_value']) + await config_std.property.setdefault(ro, 'read_only', 'append') + await config_std.property.setdefault(rw, 'read_write', 'append') + await config_std.property.read_only() + + # copy informations from 'to deploy' configuration to configuration + await config.value.importation(await config_std.value.exportation()) + await config.permissive.importation(await config_std.permissive.exportation()) + await config.property.importation(await config_std.property.exportation()) + + async def build_configuration(self, + config: Config, + ) -> dict: + configuration = {} + for option in await config.option.list('optiondescription'): + name = await option.option.name() + if name == 'services': + continue + if name == RougailConfig['variable_namespace']: + fullpath = False + flatten = True + else: + fullpath = True + flatten = False + configuration.update(await option.value.dict(leader_to_list=True, fullpath=fullpath, flatten=flatten)) + return configuration diff --git a/src/risotto/utils.py b/src/risotto/utils.py index 7282708..4a497ea 100644 --- a/src/risotto/utils.py +++ b/src/risotto/utils.py @@ -1,9 +1,27 @@ class Undefined: pass +undefined = Undefined() def _(s): return s -undefined = Undefined() +def tiramisu_display_name(kls, + dyn_name: 'Base'=None, + suffix: str=None, + ) -> str: + if dyn_name is not None: + name = dyn_name + else: + name = kls.impl_getname() + doc = kls.impl_get_information('doc', None) + if doc: + doc = str(doc) + if doc.endswith('.'): + doc = doc[:-1] + if suffix: + doc += suffix + if name != doc: + name += f' ({doc})' + return name