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 ...config import DEBUG from ...context import Context from ...utils import _ from ...error import CallError 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): 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', 'v1.session.servermodel.start'], None) async def start_session(self, risotto_context: Context, id: int) -> Dict: """ start a new config session for a server or a servermodel """ type = risotto_context.message.rsplit('.', 2)[-2] config_module = dispatcher.get_service('config') if type == 'server': if id not in config_module.server: raise Exception(_(f'cannot find {type} with id {id}')) config = config_module.server[id]['server'] else: if id not in config_module.servermodel: raise Exception(_(f'cannot find {type} with id {id}')) config = config_module.servermodel[id] storage = self.get_storage(type) # check if a session already exists sessions = storage.get_sessions() for session in sessions.values(): if sess['id'] == id: if sess['username'] == risotto_context.username: # same user so returns it return self.format_session(session['session_id'], session) else: raise CallError(_(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 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, type) @register(['v1.session.server.list', 'v1.session.servermodel.list'], None) 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'], None) 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 CallError(f'unknown mode {mode}') storage.set_config_mode(session_id, mode) if debug is not None: 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'], None) 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 option = session['option'].option(name).option if option.ismulti() and not option.isfollower(): value = value_multi namespace = session['namespace'] update = {'name': f'{namespace}.{name}', 'action': action, 'value': value} if index is not None: update['index'] = index updates = {'updates': [update]} ret = session['option'].updates(updates) if update['name'] in ret: for val in ret[update['name']][index]: if isinstance(val, ValueError): raise CallError(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'], None) 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: session['config'].forcepermissive.option(session['namespace']).value.dict() except Exception as err: raise CallError(str(err)) if type == 'server': mandatories = list(session['config'].forcepermissive.value.mandatory()) if mandatories: if len(mandatories) == 1: mandatories = mandatories[0] msg = _('the parameter "--{mandatories}" is mandatory') else: mandatories = '", "--'.join(mandatories) msg = _('parameters "{mandatories}" are mandatories') raise CallError(msg) return self.format_session(session_id, session) @register(['v1.session.server.get', 'v1.session.servermodel.get'], None) 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: info['content'] = {name: session['option'].option(name).value.get()} else: info['content'] = session['option'].value.dict() return info @register(['v1.session.server.stop', 'v1.session.servermodel.stop'], None) 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'] else: config = config_module.servermodel[id_] if save: modif_config = session['config'] config.value.importation(modif_config.value.exportation()) config.permissive.importation(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 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 session['option'].dict(remotable='all')