from os import urandom # , unlink from binascii import hexlify from traceback import print_exc from typing import Dict, List, Optional, Any from tiramisu import Storage from ...http import register as register_http from ...context import Context from ...utils import _ from .storage import storage_server, storage_servermodel from ...controller import Controller from ...register import register from ...dispatcher import dispatcher class Risotto(Controller): def __init__(self, test): self.modify_storage = Storage(engine='dictionary') def get_storage(self, type: str): if type == 'server': return storage_server return storage_servermodel def get_session(self, risotto_context: Context, session_id: str, type: str) -> Dict: """ Get session information from storage """ if type == 'server': storage = storage_server else: storage = storage_servermodel return storage.get_session(session_id, risotto_context.username) def get_session_informations(self, risotto_context: Context, session_id: str, type: str) -> Dict: """ format session with a session ID name """ session = self.get_session(risotto_context, session_id, type) return self.format_session(session_id, session) def format_session(self, session_name: str, session: Dict) -> Dict: """ format session """ return {'session_id': session_name, 'id': session['id'], 'username': session['username'], 'timestamp': session['timestamp'], 'namespace': session['namespace'], 'mode': session['mode'], 'debug': session['debug']} @register('v1.session.server.start') async def start_session_server(self, risotto_context: Context, server_name: str) -> Dict: """ start a new config session for a server """ config_module = dispatcher.get_service('config') server = await self.call('v1.server.describe', risotto_context, server_name=server_name) if not server or server['server_id'] not in config_module.server: raise Exception(_(f'cannot find server with name {server_name}')) id = server['server_id'] config = config_module.server[id]['server_to_deploy'] storage = self.get_storage('server') # check if a session already exists sessions = storage.get_sessions() for sess_id, session in sessions.items(): if session['id'] == id: if session['username'] == risotto_context.username: # same user so returns it return self.format_session(sess_id, session) else: raise Exception(_(f'{username} already edits this configuration')) # create a new session while True: session_id = 'z' + hexlify(urandom(23)).decode() if not session_id in sessions: break await storage.add_session(session_id, config, id, risotto_context.username, self.modify_storage) # return session's information return self.get_session_informations(risotto_context, session_id, 'server') @register('v1.session.servermodel.start') async def start_session_servermodel(self, risotto_context: Context, servermodel_name: str, source_name: str, release_distribution: str) -> Dict: """ start a new config session for a server or a servermodel """ config_module = dispatcher.get_service('config') servermodel = await self.call('v1.servermodel.describe', risotto_context, servermodel_name=servermodel_name, source_name=source_name, release_distribution=release_distribution) if not servermodel or servermodel['servermodel_id'] not in config_module.servermodel: raise Exception(_(f'cannot find servermodel with name {servermodel_name}')) id = servermodel['servermodel_id'] config = config_module.servermodel[id] storage = self.get_storage('servermodel') # check if a session already exists sessions = storage.get_sessions() for sess_id, session in sessions.items(): if session['id'] == id: if session['username'] == risotto_context.username: # same user so returns it return self.format_session(sess_id, session) else: raise Exception(_(f'{username} already edits this configuration')) # create a new session while True: session_id = 'z' + hexlify(urandom(23)).decode() if not session_id in sessions: break await storage.add_session(session_id, config, id, risotto_context.username, self.modify_storage) # return session's information return self.get_session_informations(risotto_context, session_id, 'servermodel') @register(['v1.session.server.list', 'v1.session.servermodel.list']) async def list_session_server(self, risotto_context: Context) -> Dict: type = risotto_context.message.rsplit('.', 2)[-2] storage = self.get_storage(type) return [self.format_session(session_id, session) for session_id, session in storage.get_sessions().items()] @register(['v1.session.server.filter', 'v1.session.servermodel.filter']) async def filter_session(self, risotto_context: Context, session_id: str, namespace: str, mode: str, debug: Optional[bool]): type = risotto_context.message.rsplit('.', 2)[-2] storage = self.get_storage(type) # to validate the session right storage.get_session(session_id, risotto_context.username) if namespace is not None: storage.set_namespace(session_id, namespace) if mode is not None: if mode not in ('basic', 'normal', 'expert'): raise Exception(f'unknown mode {mode}') await storage.set_config_mode(session_id, mode) if debug is not None: await storage.set_config_debug(session_id, debug) return self.get_session_informations(risotto_context, session_id, type) @register(['v1.session.server.configure', 'v1.session.servermodel.configure']) async def configure_session(self, risotto_context: Context, session_id: str, action: str, name: str, index: int, value: Any, value_multi: Optional[List]) -> Dict: type = risotto_context.message.rsplit('.', 2)[-2] session = self.get_session(risotto_context, session_id, type) # if multi and not follower the value is in fact in value_multi # FIXME option = session['option'].option(name).option option = session['config'].option(name).option if await option.ismulti() and not await option.isfollower(): value = value_multi #FIXME namespace = session['namespace'] #FIXME update = {'name': f'{namespace}.{name}', update = {'name': name, 'action': action, 'value': value} if index is not None: update['index'] = index updates = {'updates': [update]} ret = await session['option'].updates(updates) if update['name'] in ret: for val in ret[update['name']][index]: if isinstance(val, ValueError): raise Exception(val) ret = {'session_id': session_id, 'name': name} if index is not None: ret['index'] = index return ret @register(['v1.session.server.validate', 'v1.session.servermodel.validate']) async def validate_session(self, risotto_context: Context, session_id: str) -> Dict: type = risotto_context.message.rsplit('.', 2)[-2] session = self.get_session(risotto_context, session_id, type) try: await session['config'].forcepermissive.option(session['namespace']).value.dict() except Exception as err: raise Exception(str(err)) if type == 'server': config = session['config'] await config.property.read_only() mandatories = list(await config.value.mandatory()) await config.property.read_write() if mandatories: # FIXME mandatories = [mandatory.split('.', 1)[1] for mandatory in mandatories] if len(mandatories) == 1: mandatories = mandatories[0] msg = _(f'the parameter "--{mandatories}" is mandatory') else: mandatories = '", "--'.join(mandatories) msg = _(f'parameters "--{mandatories}" are mandatories') raise Exception(msg) return self.format_session(session_id, session) @register(['v1.session.server.get', 'v1.session.servermodel.get']) async def get_session_server(self, risotto_context: Context, session_id: str, name: Optional[str]) -> Dict: type = risotto_context.message.rsplit('.', 2)[-2] session = self.get_session(risotto_context, session_id, type) info = self.format_session(session_id, session) if name is not None: content = {name: await session['config'].option(name).value.get()} else: content = await session['option'].value.dict(fullpath=True, leader_to_list=True) info['content'] = content return info @register(['v1.session.server.stop', 'v1.session.servermodel.stop']) async def stop_session(self, risotto_context: Context, session_id: str, save: bool) -> Dict: type = risotto_context.message.rsplit('.', 2)[-2] storage = self.get_storage(type) session = storage.get_session(session_id, risotto_context.username) id_ = session['id'] config_module = dispatcher.get_service('config') if type == 'server': config = config_module.server[id_]['server_to_deploy'] else: config = config_module.servermodel[id_] if save: modif_config = session['config'] await config.value.importation(await modif_config.value.exportation()) await config.permissive.importation(await modif_config.permissive.exportation()) storage.del_session(session_id) return self.format_session(session_id, session) @register_http('v1', '/config/server/{session_id}') async def get_server_api(self, request, risotto_context: Context, session_id: str) -> Dict: session = storage_server.get_session(session_id, risotto_context.username) return await session['option'].dict(remotable='all') @register_http('v1', '/config/servermodel/{session_id}') async def get_servermodel_api(self, request, risotto_context: Context, session_id: str) -> Dict: session = storage_servermodel.get_session(session_id, risotto_context.username) return await session['option'].dict(remotable='all')