move corresponding file to risotto-setting

This commit is contained in:
Emmanuel Garette 2020-03-04 15:15:07 +01:00
parent 299a9f52a0
commit b39aac8fd2
23 changed files with 12 additions and 2343 deletions

View File

@ -1,110 +1,24 @@
import asyncpg import asyncpg
import asyncio import asyncio
from os.path import isfile
from sys import init
from risotto.config import get_config from risotto.config import get_config
VERSION_INIT = """
-- Source
CREATE TABLE Source (
SourceId SERIAL PRIMARY KEY,
SourceName VARCHAR(255) NOT NULL UNIQUE,
SourceURL TEXT
);
-- Release
CREATE TABLE Release (
ReleaseId SERIAL PRIMARY KEY,
ReleaseName VARCHAR(255) NOT NULL,
ReleaseSourceId INTEGER NOT NULL,
ReleaseDistribution VARCHAR(20) CONSTRAINT releasedistribution_choice CHECK (ReleaseDistribution IN ('last', 'n-1', 'n-2')),
UNIQUE (ReleaseName, ReleaseSourceId),
UNIQUE (ReleaseDistribution, ReleaseSourceId),
FOREIGN KEY (ReleaseSourceId) REFERENCES Source(SourceId)
);
-- Servermodel
CREATE TABLE Servermodel (
ServermodelId SERIAL PRIMARY KEY,
ServermodelName VARCHAR(255) NOT NULL,
ServermodelDescription VARCHAR(255) NOT NULL,
ServermodelParentsId INTEGER [] DEFAULT '{}',
ServermodelReleaseId INTEGER NOT NULL,
ServermodelApplicationserviceId INTEGER NOT NULL,
UNIQUE (ServermodelName, ServermodelReleaseId)
);
CREATE INDEX ServermodelApplicationserviceId_index ON Servermodel (ServermodelApplicationserviceId);
-- Applicationservice
CREATE TABLE Applicationservice (
ApplicationserviceId SERIAL PRIMARY KEY,
ApplicationserviceName VARCHAR(255) NOT NULL,
ApplicationserviceDescription VARCHAR(255) NOT NULL,
ApplicationserviceReleaseId INTEGER NOT NULL,
UNIQUE (ApplicationserviceName, ApplicationserviceReleaseId)
);
CREATE TABLE ApplicationserviceDependency (
ApplicationserviceId INTEGER NOT NULL,
ApplicationserviceDependencyId INTEGER NOT NULL,
UNIQUE(ApplicationserviceId, ApplicationserviceDependencyId),
FOREIGN KEY (ApplicationserviceId) REFERENCES Applicationservice(ApplicationserviceId),
FOREIGN KEY (ApplicationserviceDependencyId) REFERENCES Applicationservice(ApplicationserviceId)
);
-- Server
CREATE TABLE Server (
ServerId SERIAL PRIMARY KEY,
ServerName VARCHAR(255) NOT NULL UNIQUE,
ServerDescription VARCHAR(255) NOT NULL,
ServerServermodelId INTEGER NOT NULL
);
-- User, Role and ACL
CREATE TABLE RisottoUser (
UserId SERIAL PRIMARY KEY,
UserLogin VARCHAR(100) NOT NULL UNIQUE,
UserName VARCHAR(100) NOT NULL,
UserSurname VARCHAR(100) NOT NULL
);
CREATE TABLE UserRole (
RoleId SERIAL PRIMARY KEY,
RoleUserId INTEGER NOT NULL,
RoleName VARCHAR(255) NOT NULL,
RoleAttribute VARCHAR(255),
RoleAttributeValue VARCHAR(255),
FOREIGN KEY (RoleUserId) REFERENCES RisottoUser(UserId)
);
CREATE TABLE URI (
URIId SERIAL PRIMARY KEY,
URIName VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE RoleURI (
RoleName VARCHAR(255) NOT NULL,
URIId INTEGER NOT NULL,
FOREIGN KEY (URIId) REFERENCES URI(URIId),
PRIMARY KEY (RoleName, URIId)
);
-- Log
CREATE TABLE log(
Msg VARCHAR(255) NOT NULL,
Level VARCHAR(10) NOT NULL,
Path VARCHAR(255),
Username VARCHAR(100) NOT NULL,
Data JSON,
Date timestamp DEFAULT current_timestamp
);
"""
async def main(): async def main():
sql_filename = get_config()['global']['sql_filename']
if not isfile(sql_filename):
print('no sql file to import')
exit()
db_conf = get_config()['database']['dsn'] db_conf = get_config()['database']['dsn']
pool = await asyncpg.create_pool(db_conf) pool = await asyncpg.create_pool(db_conf)
async with pool.acquire() as connection: async with pool.acquire() as connection:
async with connection.transaction(): async with connection.transaction():
returns = await connection.execute(VERSION_INIT) with open(sql_filename, 'r') as sql:
await connection.execute(sql.read().decode())
if __name__ == '__main__': if __name__ == '__main__':
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.run_until_complete(main()) loop.run_until_complete(main())
# asyncio.run(main())

View File

@ -8,6 +8,7 @@ DEFAULT_DSN = environ.get('RISOTTO_DSN', 'postgres:///risotto?host=/var/run/post
DEFAULT_TIRAMISU_DSN = environ.get('DEFAULT_TIRAMISU_DSN', 'postgres:///tiramisu?host=/var/run/postgresql/&user=tiramisu') DEFAULT_TIRAMISU_DSN = environ.get('DEFAULT_TIRAMISU_DSN', 'postgres:///tiramisu?host=/var/run/postgresql/&user=tiramisu')
MESSAGE_PATH = environ.get('MESSAGE_PATH', '/root/risotto-message/messages') MESSAGE_PATH = environ.get('MESSAGE_PATH', '/root/risotto-message/messages')
MODULE_NAME = environ.get('MODULE_NAME', 'test') MODULE_NAME = environ.get('MODULE_NAME', 'test')
SQL_FILENAME = f'/root/risotto-{MODULE_NAME}/sql/init.sql'
def get_config(): def get_config():
@ -22,6 +23,7 @@ def get_config():
'check_role': True, 'check_role': True,
'admin_user': DEFAULT_USER, 'admin_user': DEFAULT_USER,
'module_name': MODULE_NAME, 'module_name': MODULE_NAME,
'sql_filename': SQL_FILENAME,
'version': 'v1'}, 'version': 'v1'},
'source': {'root_path': '/srv/seed'}, 'source': {'root_path': '/srv/seed'},
'cache': {'root_path': '/var/cache/risotto'}, 'cache': {'root_path': '/var/cache/risotto'},

View File

@ -1,20 +0,0 @@
from os import listdir
from os.path import isdir, isfile, dirname, abspath, basename, join
from importlib import import_module
from ..dispatcher import dispatcher
def load_services(modules=None,
validate: bool=True,
test: bool=False):
abs_here = dirname(abspath(__file__))
here = basename(abs_here)
module = basename(dirname(abs_here))
if not modules:
modules = listdir(abs_here)
for filename in modules:
absfilename = join(abs_here, filename)
if isdir(absfilename) and isfile(join(absfilename, '__init__.py')):
dispatcher.set_module(filename, import_module(f'.{here}.{filename}', module), test)
if validate:
dispatcher.validate()

View File

@ -1 +0,0 @@
from .applicationservice import Risotto

View File

@ -1,248 +0,0 @@
from os import listdir
from os.path import join
from traceback import print_exc
from yaml import load, SafeLoader
from typing import Dict, List, Set
from ...controller import Controller
from ...register import register
from ...config import get_config
from ...error import ExecutionError
from ...context import Context
from ...utils import _
class Risotto(Controller):
def __init__(self,
test: bool) -> None:
self.source_root_path = get_config().get('source').get('root_path')
self.internal_source_name = get_config()['servermodel']['internal_source']
self.internal_distribution_name = get_config()['servermodel']['internal_distribution']
self.internal_release_name = get_config()['servermodel']['internal_release_name']
super().__init__(test)
async def on_join(self,
risotto_context: Context) -> None:
internal_source = await self.call('v1.setting.source.create',
risotto_context,
source_name=self.internal_source_name,
source_url='none')
internal_release = await self.call('v1.setting.source.release.create',
risotto_context,
source_name=self.internal_source_name,
release_name=self.internal_release_name,
release_distribution=self.internal_distribution_name)
self.internal_release_id = internal_release['release_id']
async def _applicationservice_create(self,
risotto_context: Context,
applicationservice_name: str,
applicationservice_description: str,
applicationservice_dependencies: List[int],
release_id: int) -> Dict:
applicationservice_update_query = '''INSERT INTO Applicationservice(ApplicationserviceName, ApplicationserviceDescription, ApplicationserviceReleaseId)
VALUES ($1,$2,$3)
RETURNING ApplicationserviceId
'''
applicationservice_id = await risotto_context.connection.fetchval(applicationservice_update_query,
applicationservice_name,
applicationservice_description,
release_id)
await self.insert_dependency(risotto_context,
applicationservice_id,
applicationservice_dependencies)
return {'applicationservice_name': applicationservice_name,
'applicationservice_description': applicationservice_description,
'applicationservice_release_id': release_id,
'applicationservice_id': applicationservice_id}
async def insert_dependency(self,
risotto_context: Context,
applicationservice_id: int,
dependencies: list) -> None:
sql = '''INSERT INTO ApplicationserviceDependency(ApplicationserviceId, ApplicationserviceDependencyId)
VALUES ($1, $2)'''
for dependency in dependencies:
await risotto_context.connection.execute(sql,
applicationservice_id,
dependency)
@register('v1.setting.applicationservice.dependency.add')
async def applicationservice_dependency_add(self,
risotto_context: Context,
applicationservice_name: str,
applicationservice_dependency: str,
source_name: str,
release_distribution: str) -> Dict:
release = await self.call('v1.setting.source.release.describe',
risotto_context,
source_name=source_name,
release_distribution=release_distribution)
as_descr = await self._applicationservice_describe(risotto_context,
applicationservice_name,
self.internal_release_id)
dependency_descr = await self._applicationservice_describe(risotto_context,
applicationservice_dependency,
release['release_id'])
sql = '''SELECT ApplicationserviceDependencyId
FROM ApplicationserviceDependency
WHERE ApplicationserviceId = $1 AND ApplicationserviceDependencyId = $2'''
if await risotto_context.connection.fetchrow(sql,
as_descr['applicationservice_id'],
dependency_descr['applicationservice_id']):
raise Exception(_(f'{applicationservice_name} has already a dependency to {applicationservice_dependency}'))
await self.insert_dependency(risotto_context,
as_descr['applicationservice_id'],
[dependency_descr['applicationservice_id']])
await self.publish('v1.setting.applicationservice.updated',
risotto_context,
**as_descr)
await self.updated_related_applicationservice(risotto_context,
as_descr['applicationservice_id'])
return as_descr
async def updated_related_applicationservice(self,
risotto_context: Context,
applicationservice_id: int) -> None:
sql = """
SELECT ApplicationserviceId as applicationservice_id
FROM ApplicationserviceDependency
WHERE ApplicationserviceDependencyId = $1"""
for dependency in await risotto_context.connection.fetch(sql,
applicationservice_id):
dependency_id = dependency['applicationservice_id']
applicationservice = await self._applicationservice_get_by_id(risotto_context,
dependency_id)
await self.publish('v1.setting.applicationservice.updated',
risotto_context,
**applicationservice)
await self.updated_related_applicationservice(risotto_context,
dependency_id)
async def get_dependencies(self,
risotto_context: Context,
applicationservice_id: int) -> List[int]:
dependencies = set()
sql = """
SELECT ApplicationserviceDependencyId as applicationservice_dependency_id
FROM ApplicationserviceDependency
WHERE ApplicationserviceId = $1"""
for dependency in await risotto_context.connection.fetch(sql,
applicationservice_id):
dependencies.add(dependency['applicationservice_dependency_id'])
for dependency in dependencies.copy():
dependencies |= await self.get_dependencies(risotto_context,
dependency)
return dependencies
@register('v1.setting.applicationservice.create')
async def applicationservice_create(self,
risotto_context: Context,
applicationservice_name: str,
applicationservice_description: str,
applicationservice_dependencies: List[int]) -> Dict:
applicationservice = await self._applicationservice_create(risotto_context,
applicationservice_name,
applicationservice_description,
applicationservice_dependencies,
self.internal_release_id)
dependencies = list(await self.get_dependencies(risotto_context,
applicationservice['applicationservice_id']))
applicationservice['applicationservice_dependencies'] = dependencies
return applicationservice
@register('v1.setting.applicationservice.dataset.updated')
async def applicationservice_update(self,
risotto_context: Context,
source_name: str,
release_distribution: str) -> Dict:
release = await self.call('v1.setting.source.release.describe',
risotto_context,
source_name=source_name,
release_distribution=release_distribution)
applicationservice_path = join(self.source_root_path,
source_name,
release['release_name'],
'applicationservice')
release_id = release['release_id']
for service in listdir(applicationservice_path):
try:
applicationservice_description_path = join(applicationservice_path,
service,
'applicationservice.yml')
with open(applicationservice_description_path, 'r') as applicationservice_yml:
applicationservice_description = load(applicationservice_yml,
Loader=SafeLoader)
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
raise ExecutionError(_(f'Error while reading {applicationservice_description_path}: {err}'))
try:
await self._applicationservice_create(risotto_context,
applicationservice_description['name'],
applicationservice_description['description'],
[],
release_id)
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
raise ExecutionError(_(f"Error while injecting application service {applicationservice_description['name']} in database: {err}"))
return {'retcode': 0,
'returns': _('Application Services successfully loaded')}
@register('v1.setting.applicationservice.get_by_id')
async def applicationservice_get_by_id(self,
risotto_context: Context,
applicationservice_id: int) -> Dict:
return await self._applicationservice_get_by_id(risotto_context,
applicationservice_id)
async def _applicationservice_get_by_id(self,
risotto_context: Context,
applicationservice_id: int) -> Dict:
applicationservice_query = """
SELECT ApplicationserviceId as applicationservice_id, ApplicationserviceName as applicationservice_name, ApplicationserviceReleaseId as applicationservice_release_id
FROM applicationservice
WHERE ApplicationserviceId=$1"""
applicationservice = await risotto_context.connection.fetchrow(applicationservice_query,
applicationservice_id)
if applicationservice is None:
raise Exception(_(f'unknown service with ID {applicationservice_id}'))
dependencies = list(await self.get_dependencies(risotto_context,
applicationservice['applicationservice_id']))
applicationservice = dict(applicationservice)
applicationservice['applicationservice_dependencies'] = dependencies
return applicationservice
async def _applicationservice_describe(self,
risotto_context: Context,
applicationservice_name,
release_id):
applicationservice_query = """
SELECT ApplicationserviceId as applicationservice_id, ApplicationserviceName as applicationservice_name, ApplicationserviceReleaseId as applicationservice_release_id
FROM Applicationservice
WHERE ApplicationserviceName=$1 AND ApplicationserviceReleaseId=$2"""
applicationservice = await risotto_context.connection.fetchrow(applicationservice_query,
applicationservice_name,
release_id)
if applicationservice is None:
raise Exception(_(f'unknown service {applicationservice_name} in release ID {release_id}'))
return dict(applicationservice)
@register('v1.setting.applicationservice.describe')
async def applicationservice_describe(self,
risotto_context: Context,
applicationservice_name,
source_name,
release_distribution):
release = await self.call('v1.setting.source.release.describe',
risotto_context,
source_name=source_name,
release_distribution=release_distribution)
applicationservice = await self._applicationservice_describe(risotto_context,
applicationservice_name,
release['release_id'])
dependencies = list(await self.get_dependencies(risotto_context,
applicationservice['applicationservice_id']))
applicationservice['applicationservice_dependencies'] = dependencies
return applicationservice

View File

@ -1 +0,0 @@
from .config import Risotto

View File

@ -1,428 +0,0 @@
from lxml.etree import parse
from io import BytesIO
from os import unlink
from os.path import isdir, isfile, join
from traceback import print_exc
from typing import Dict, List
from tiramisu import Storage, MixConfig
from tiramisu.error import PropertiesOptionError
from rougail import load as rougail_load
from rougail.config import dtdfilename
from ...controller import Controller
from ...register import register
from ...config import get_config
from ...context import Context
from ...utils import _
from ...error import CallError, RegistrationError
from ...logger import log
class Risotto(Controller):
def __init__(self,
test) -> None:
global conf_storage
self.cache_root_path = join(get_config().get('cache').get('root_path'), 'servermodel')
if not isdir(self.cache_root_path):
raise RegistrationError(_(f'unable to find the cache dir "{self.cache_root_path}"'))
if not test:
db_conf = get_config()['database']['tiramisu_dsn']
self.save_storage = Storage(engine='postgres')
self.save_storage.setting(dsn=db_conf)
self.servermodel = {}
self.server = {}
super().__init__(test)
async def on_join(self,
risotto_context: Context) -> None:
""" pre-load servermodel and server
"""
await self.load_servermodels(risotto_context)
await self.load_servers(risotto_context)
async def load_servermodels(self,
risotto_context: Context) -> None:
""" load all available servermodels
"""
await log.info_msg(risotto_context,
None,
'Load servermodels')
servermodels = await self.call('v1.setting.servermodel.list',
risotto_context)
# load each servermodels
for servermodel in servermodels:
try:
await self.load_servermodel(risotto_context,
servermodel['servermodel_id'],
servermodel['servermodel_name'])
except CallError as err:
pass
# do link to this servermodel
for servermodel in servermodels:
if 'servermodel_parents_id' in servermodel:
for servermodelparentid in servermodel['servermodel_parents_id']:
await self.servermodel_legacy(risotto_context,
servermodel['servermodel_name'],
servermodel['servermodel_id'],
servermodelparentid)
def get_funcs_filename(self,
servermodel_id: int):
return join(self.cache_root_path, str(servermodel_id), "funcs.py")
async def load_servermodel(self,
risotto_context: Context,
servermodel_id: int,
servermodel_name: str) -> None:
""" Loads a servermodel
"""
cache_file = join(self.cache_root_path, str(servermodel_id), "dictionaries.xml")
funcs_file = self.get_funcs_filename(servermodel_id)
await log.info_msg(risotto_context,
None,
f'Load servermodel {servermodel_name} ({servermodel_id})')
# use file in cache
with open(cache_file) as fileio:
xmlroot = parse(fileio).getroot()
try:
self.servermodel[servermodel_id] = await self.build_mixconfig(servermodel_id,
servermodel_name,
xmlroot,
funcs_file)
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
msg = _(f'unable to load {servermodel_name}: {err}')
await log.error_msg(risotto_context,
None,
msg)
async def build_mixconfig(self,
servermodel_id: int,
servermodel_name: str,
xmlroot: str,
funcs_file: str) -> MixConfig:
""" Build mixconfig for a servermodel
"""
# build tiramisu's session ID
session_id = f'v_{servermodel_id}'
optiondescription = rougail_load(xmlroot,
dtdfilename,
funcs_file)
# build servermodel mixconfig (v_xxx)
mixconfig = await MixConfig(children=[],
optiondescription=optiondescription,
session_id=session_id,
storage=self.save_storage)
# change default rights
ro_origin = await mixconfig.property.getdefault('read_only', 'append')
ro_append = frozenset(ro_origin - {'force_store_value'})
rw_origin = await mixconfig.property.getdefault('read_write', 'append')
rw_append = frozenset(rw_origin - {'force_store_value'})
await mixconfig.property.setdefault(ro_append, 'read_only', 'append')
await mixconfig.property.setdefault(rw_append, 'read_write', 'append')
await mixconfig.property.read_only()
await mixconfig.permissive.add('basic')
await mixconfig.permissive.add('normal')
await mixconfig.permissive.add('expert')
# set informtion and owner
await mixconfig.owner.set('v_{}'.format(servermodel_name))
await mixconfig.information.set('servermodel_id', servermodel_id)
await mixconfig.information.set('servermodel_name', servermodel_name)
# return configuration
return mixconfig
async def servermodel_legacy(self,
risotto_context: Context,
servermodel_name: str,
servermodel_id: int,
servermodel_parent_id: int) -> None:
""" Make link between parent and children
"""
if servermodel_parent_id is None:
return
if not self.servermodel.get(servermodel_parent_id):
msg = _(f'Servermodel with id {servermodel_parent_id} not loaded, skipping legacy for servermodel {servermodel_name} ({servermodel_id})')
await log.error_msg(risotto_context,
None,
msg)
return
servermodel_parent = self.servermodel[servermodel_parent_id]
servermodel_parent_name = await servermodel_parent.information.get('servermodel_name')
msg = _(f'Create legacy of servermodel {servermodel_name} ({servermodel_id}) with parent {servermodel_parent_name} ({servermodel_parent_id})')
await log.info_msg(risotto_context,
None,
msg)
# do link
try:
await servermodel_parent.config.add(self.servermodel[servermodel_id])
except Exception as err:
await log.error_msg(risotto_context,
None,
str(err))
async def load_servers(self,
risotto_context: Context) -> None:
""" load all available servers
"""
await log.info_msg(risotto_context,
None,
f'Load servers')
# get all servers
servers = await self.call('v1.setting.server.list',
risotto_context)
# loads servers
for server in servers:
try:
if server['server_id'] in self.server:
return
await self.load_server(risotto_context,
server['server_id'],
server['server_name'],
server['server_servermodel_id'])
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
server_name = server['server_name']
server_id = server['server_id']
msg = _(f'unable to load server {server_name} ({server_id}): {err}')
await log.error_msg(risotto_context,
None,
msg)
async def load_server(self,
risotto_context: Context,
server_id: int,
server_name: str,
server_servermodel_id: int) -> None:
""" Loads a server
"""
await log.info_msg(risotto_context,
None,
f'Load server {server_name} ({server_id})')
if not server_servermodel_id in self.servermodel:
msg = f'unable to find servermodel with id {server_servermodel_id}'
await log.error_msg(risotto_context,
None,
msg)
raise CallError(msg)
# check if server was already created
session_id = f's_{server_id}'
# get the servermodel's mixconfig
mixconfig = self.servermodel[server_servermodel_id]
# create server configuration and store it
self.server[server_id] = {'server': await self.build_config(session_id,
server_id,
server_name,
mixconfig),
'server_to_deploy': await self.build_config(f'std_{server_id}',
server_id,
server_name,
mixconfig,
std=True),
'funcs_file': self.get_funcs_filename(server_servermodel_id)}
async def build_config(self,
session_id: str,
server_id: int,
server_name: str,
mixconfig: MixConfig,
std: bool=False) -> None:
""" build server's config
"""
config = await mixconfig.config.new(session_id,
storage=self.save_storage)
await config.information.set('server_id', server_id)
await config.information.set('server_name', server_name)
option_value = config.option('creole.interface_0.domain_name_eth0').value
if std:
try:
await option_value.get()
except PropertiesOptionError:
await config.owner.set(server_name)
await config.property.read_write()
await config.option('creole.interface_0.domain_name_eth0').value.set(server_name)
await config.property.read_only()
return config
@register('v1.setting.server.created')
async def server_created(self,
risotto_context: Context,
server_id: int,
server_name: str,
server_servermodel_id: int) -> None:
""" Loads server's configuration when a new server is created
"""
if server_id in self.server:
return
await self.load_server(risotto_context,
server_id,
server_name,
server_servermodel_id)
@register('v1.setting.server.deleted')
async def server_deleted(self,
server_id: int) -> None:
for server_type in ['server', 'server_to_deploy']:
config = self.server[server_id]['server']
for parent in await config.config.parents():
await parent.config.pop(await config.config.name())
await config.session.reset()
del self.server[server_id]
@register('v1.setting.servermodel.created')
async def servermodel_created(self,
risotto_context: Context,
servermodel_id: int,
servermodel_name: str,
servermodel_parents_id: List[int]) -> None:
""" when servermodels are created, load it and do link
"""
await self.load_and_link_servermodel(risotto_context,
servermodel_id,
servermodel_name,
servermodel_parents_id)
async def load_and_link_servermodel(self,
risotto_context: Context,
servermodel_id: int,
servermodel_name: str,
servermodel_parents_id: List[int]) -> None:
await self.load_servermodel(risotto_context,
servermodel_id,
servermodel_name)
if servermodel_parents_id is not None:
for servermodelparentid in servermodel_parents_id:
await self.servermodel_legacy(risotto_context,
servermodel_name,
servermodel_id,
servermodelparentid)
async def servermodel_delete(self,
servermodel_id: int) -> List[MixConfig]:
mixconfig = self.servermodel.pop(servermodel_id)
children = []
for child in await mixconfig.config.list():
if not (await child.session.id()).startswith('std_'):
children.append(child)
await mixconfig.config.pop(await child.session.id())
for parent in await mixconfig.config.parents():
await parent.config.pop(await mixconfig.session.id())
return children
@register('v1.setting.servermodel.updated')
async def servermodel_updated(self,
risotto_context: Context,
servermodel_id: int,
servermodel_name: str,
servermodel_parents_id: List[int]) -> None:
await log.info_msg(risotto_context,
None,
f'Reload servermodel {servermodel_name} ({servermodel_id})')
# store all informations
if servermodel_id in self.servermodel:
children = await self.servermodel_delete(servermodel_id)
else:
children = []
# create new one
await self.load_and_link_servermodel(risotto_context,
servermodel_id,
servermodel_name,
servermodel_parents_id)
# recreate link to children
for child in children:
if await child.config.type() == 'config':
server_id = await child.information.get('server_id')
server_name = await child.information.get('server_name')
await self.load_server(risotto_context,
server_id,
server_name,
servermodel_id)
else:
await self.servermodel[servermodel_id].config.add(child)
@register('v1.setting.config.configuration.server.get')
async def get_configuration(self,
risotto_context: Context,
server_name: str,
deployed: bool) -> dict:
server = await self.call('v1.setting.server.describe',
risotto_context,
server_name=server_name)
server_id = server['server_id']
if server_id not in self.server:
msg = _(f'cannot find server with id {server_id}')
await log.error_msg(risotto_context,
None,
msg)
raise CallError(msg)
if deployed:
server = self.server[server_id]['server']
else:
server = self.server[server_id]['server_to_deploy']
await server.property.read_only()
try:
configuration = await server.value.dict(fullpath=True,
leader_to_list=True)
except:
if deployed:
msg = _(f'No configuration available for server {server_id}')
else:
msg = _(f'No undeployed configuration available for server {server_id}')
await log.error_msg(risotto_context,
None,
msg)
raise CallError(msg)
return {'server_name': server_name,
'deployed': deployed,
'configuration': configuration}
@register('v1.setting.config.configuration.server.deploy')
async def deploy_configuration(self,
risotto_context: Context,
server_name: str) -> Dict:
"""Copy values, permissions, permissives from config 'to deploy' to active config
"""
server = await self.call('v1.setting.server.describe',
risotto_context,
server_name=server_name)
server_id = server['server_id']
# FIXME is server_to_deploy working?
config = self.server[server_id]['server']
config_std = self.server[server_id]['server_to_deploy']
# when deploy, calculate force_store_value
ro = await config_std.property.getdefault('read_only', 'append')
if 'force_store_value' not in ro:
ro = frozenset(list(ro) + ['force_store_value'])
await config_std.property.setdefault(ro, 'read_only', 'append')
rw = await config_std.property.getdefault('read_write', 'append')
rw = frozenset(list(rw) + ['force_store_value'])
await config_std.property.setdefault(rw, 'read_write', 'append')
await config_std.property.add('force_store_value')
# copy informations from server 'to deploy' configuration to server 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())
return {'server_id': server_id,
'server_name': server_name,
'deployed': True}

View File

@ -1 +0,0 @@
from .server import Risotto

View File

@ -1,71 +0,0 @@
from typing import Dict
from tiramisu import DomainnameOption
from ...controller import Controller
from ...register import register
from ...context import Context
from ...config import get_config
from ...utils import _
class Risotto(Controller):
def __init__(self,
test: bool) -> None:
self.internal_source_name = get_config()['servermodel']['internal_source']
@register('v1.setting.server.list')
async def server_list(self,
risotto_context: Context) -> Dict:
sql = '''
SELECT ServerId as server_id, ServerName as server_name, ServerDescription as server_description, ServerServermodelId as server_servermodel_id
FROM Server
'''
servers = await risotto_context.connection.fetch(sql)
return [dict(r) for r in servers]
@register('v1.setting.server.create', 'v1.setting.server.created')
async def server_create(self,
risotto_context: Context,
server_name: str,
server_description: str,
servermodel_name: str,
release_distribution: str) -> Dict:
DomainnameOption('server_name', _('Server name'), server_name)
servermodel = await self.call('v1.setting.servermodel.describe',
risotto_context,
servermodel_name=servermodel_name,
source_name=self.internal_source_name,
release_distribution=release_distribution)
server_insert = """INSERT INTO Server(ServerName, ServerDescription, ServerServermodelId)
VALUES ($1,$2,$3)
RETURNING ServerId
"""
server_id = await risotto_context.connection.fetchval(server_insert,
server_name,
server_description,
servermodel['servermodel_id'])
await self.call('v1.setting.user.role.create',
risotto_context,
user_login=risotto_context.username,
role_name='server_rw',
role_attribute='Server.ServerName',
role_attribute_value=server_name)
return {'server_id': server_id,
'server_name': server_name,
'server_description': server_description,
'server_servermodel_id': servermodel['servermodel_id']}
@register('v1.setting.server.describe')
async def server_describe(self,
risotto_context: Context,
server_name: str) -> Dict:
sql = '''
SELECT ServerId as server_id, ServerName as server_name, ServerDescription as server_description, ServerServermodelId as server_servermodel_id
FROM Server
WHERE ServerName = $1
'''
server = await risotto_context.connection.fetchrow(sql,
server_name)
if not server:
raise Exception(_(f'unable to find server with name {server_name}'))
return dict(server)

View File

@ -1 +0,0 @@
from .servermodel import Risotto

View File

@ -1,172 +0,0 @@
from os.path import join, isdir, isfile
from os import listdir, makedirs
from shutil import rmtree, copyfile
from typing import Dict, List, Optional
from rougail import CreoleObjSpace
from rougail.config import dtdfilename
from ...controller import Controller
from ...context import Context
from ...logger import log
from ...utils import _
class Generator(Controller):
async def generate(self,
risotto_context: Context,
servermodel_name: str,
servermodel_id: int,
dependencies: List[int],
generate_cache: Optional[Dict]=None) -> None:
if generate_cache is None:
generate_cache = {'applicationservice': {},
'release_id': {}}
await self.servermodel_gen_funcs(servermodel_name,
servermodel_id,
dependencies,
generate_cache,
risotto_context)
await self.servermodel_gen_schema(servermodel_name,
servermodel_id,
dependencies,
generate_cache,
risotto_context)
await self.servermodel_copy_templates(servermodel_name,
servermodel_id,
dependencies,
generate_cache,
risotto_context)
async def servermodel_gen_funcs(self,
servermodel_name: str,
servermodel_id: int,
dependencies: Dict,
generate_cache: Dict,
risotto_context: Context) -> None:
as_names = []
dest_file = self.get_servermodel_cache(servermodel_id, 'funcs.py')
with open(dest_file, 'wb') as funcs:
funcs.write(b'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\n\n')
for dependency in dependencies:
if dependency not in generate_cache['applicationservice']:
applicationservice = await self.call('v1.setting.applicationservice.get_by_id',
risotto_context,
applicationservice_id=dependency)
generate_cache['applicationservice'][dependency] = (applicationservice['applicationservice_name'],
applicationservice['applicationservice_release_id'])
applicationservice_name, release_id = generate_cache['applicationservice'][dependency]
if release_id not in generate_cache['release_id']:
release = await self.call('v1.setting.source.release.get_by_id',
risotto_context,
release_id=release_id)
generate_cache['release_id'][release_id] = (release['source_name'],
release['release_name'])
source_name, release_name = generate_cache['release_id'][release_id]
path = join(self.source_root_path,
source_name,
release_name,
'applicationservice',
applicationservice_name,
'funcs')
if isdir(path):
as_names.append(applicationservice_name)
for fil in listdir(path):
if not fil.endswith('.py'):
continue
fil_path = join(path, fil)
with open(fil_path, 'rb') as fh:
funcs.write(f'# {fil_path}\n'.encode())
funcs.write(fh.read())
funcs.write(b'\n')
as_names_str = '", "'.join(as_names)
await log.info(risotto_context,
_(f'gen funcs for "{servermodel_name}" with application services "{as_names_str}"'))
async def servermodel_gen_schema(self,
servermodel_name: str,
servermodel_id: int,
dependencies: Dict,
generate_cache: Dict,
risotto_context: Context) -> None:
paths = []
extras = []
as_names = set()
for dependency in dependencies:
applicationservice_name, release_id = generate_cache['applicationservice'][dependency]
source_name, release_name = generate_cache['release_id'][release_id]
# load creole dictionaries
path = join(self.source_root_path,
source_name,
release_name,
'applicationservice',
applicationservice_name,
'dictionaries')
if isdir(path):
as_names.add(applicationservice_name)
paths.append(path)
# load extra dictionaries
path = join(self.source_root_path,
source_name,
release_name,
'applicationservice',
applicationservice_name,
'extras')
if isdir(path):
for namespace in listdir(path):
extra_dir = join(path, namespace)
if not isdir(extra_dir):
continue
as_names.add(applicationservice_name)
extras.append((namespace, [extra_dir]))
eolobj = CreoleObjSpace(dtdfilename)
as_names_str = '", "'.join(as_names)
await log.info(risotto_context,
_(f'gen schema for "{servermodel_name}" with application services "{as_names_str}"'))
eolobj.create_or_populate_from_xml('creole', paths)
for extra in extras:
eolobj.create_or_populate_from_xml(extra[0], extra[1])
# FIXME extra
funcs_file = self.get_servermodel_cache(servermodel_id, 'funcs.py')
eolobj.space_visitor(funcs_file)
dest_dir = self.get_servermodel_cache(servermodel_id, 'dictionaries.xml')
eolobj.save(dest_dir)
def get_servermodel_cache(self,
servermodel_id: int,
subdir: Optional[str]=None) -> str:
if subdir:
return join(self.cache_root_path, str(servermodel_id), subdir)
return join(self.cache_root_path, str(servermodel_id))
async def servermodel_copy_templates(self,
servermodel_name: str,
servermodel_id: int,
dependencies: Dict,
generate_cache: Dict,
risotto_context: Context) -> None:
as_names = []
dest_dir = self.get_servermodel_cache(servermodel_id, 'templates')
if isdir(dest_dir):
rmtree(dest_dir)
makedirs(dest_dir)
for dependency in dependencies:
applicationservice_name, release_id = generate_cache['applicationservice'][dependency]
source_name, release_name = generate_cache['release_id'][release_id]
path = join(self.source_root_path,
source_name,
release_name,
'applicationservice',
applicationservice_name,
'templates')
if isdir(path):
for template in listdir(path):
template_path = join(dest_dir, template)
if isfile(template_path):
as_names_str = '", "'.join(as_names)
raise Exception(_(f'duplicate "{template}" when copying template from "{applicationservice_name}" to "{dest_dir}" for servermodel "{servermodel_name}" (previous application services was "{as_names_str}"'))
copyfile(join(path, template), template_path)
as_names.append(applicationservice_name)
as_names_str = '", "'.join(as_names)
await log.info(risotto_context,
_(f'copy templates for "{servermodel_name}" with application services "{as_names_str}"'))

View File

@ -1,292 +0,0 @@
from shutil import rmtree
from os import listdir, makedirs
from os.path import join, isdir
from yaml import load, SafeLoader
from traceback import print_exc
from typing import Dict, List, Optional
from .generator import Generator
from ...register import register
from ...utils import _
from ...context import Context
from ...config import get_config
from ...error import ExecutionError
from ...logger import log
class Risotto(Generator):
def __init__(self,
test: bool) -> None:
self.source_root_path = get_config()['source']['root_path']
self.cache_root_path = join(get_config()['cache']['root_path'], 'servermodel')
self.internal_source_name = get_config()['servermodel']['internal_source']
self.internal_distribution_name = get_config()['servermodel']['internal_distribution']
self.internal_release_name = get_config()['servermodel']['internal_release_name']
if not isdir(self.cache_root_path):
makedirs(join(self.cache_root_path))
super().__init__(test)
async def on_join(self,
risotto_context: Context) -> None:
print('===', await self.call('v1.pki.openssh.get', risotto_context))
internal_release = await self.call('v1.setting.source.release.describe',
risotto_context,
source_name=self.internal_source_name,
release_distribution=self.internal_distribution_name)
self.internal_release_id = internal_release['release_id']
async def _servermodel_create(self,
risotto_context: Context,
servermodel_name: str,
servermodel_description: str,
servermodel_parents: List[Dict],
dependencies: List[int],
release_id: int,
generate_cache: Dict=None) -> Dict:
if generate_cache is None:
generate_cache = {'applicationservice': {},
'release_id': {}}
servermodel_insert = """INSERT INTO Servermodel(ServermodelName, ServermodelDescription, ServermodelParentsId, ServermodelReleaseId, ServermodelApplicationServiceId)
VALUES ($1,$2,$3,$4,$5)
RETURNING ServermodelId
"""
as_name = f"local_{servermodel_name}"
as_description = f'local application service for {servermodel_name}'
servermodel_parents_id = []
for servermodel_parent in servermodel_parents:
servermodel_parents_id.append(servermodel_parent['servermodel_id'])
dependencies.append(servermodel_parent['servermodel_applicationservice_id'])
applicationservice = await self.call('v1.setting.applicationservice.create',
risotto_context,
applicationservice_name=as_name,
applicationservice_description=as_description,
applicationservice_dependencies=dependencies)
applicationservice_id = applicationservice['applicationservice_id']
generate_cache['applicationservice'][applicationservice_id] = (as_name,
self.internal_release_id)
if self.internal_release_id not in generate_cache['release_id']:
generate_cache['release_id'][self.internal_release_id] = (self.internal_source_name,
self.internal_release_name)
servermodel_id = await risotto_context.connection.fetchval(servermodel_insert,
servermodel_name,
servermodel_description,
servermodel_parents_id,
release_id,
applicationservice_id)
dest_dir = self.get_servermodel_cache(servermodel_id)
if isdir(dest_dir):
rmtree(dest_dir)
makedirs(dest_dir)
dependencies = applicationservice['applicationservice_dependencies']
# for as_release_id in dependencies.values():
# applicationservice_name, as_release_id = applicationservice_infos
# if as_release_id not in release_cache:
# release_cache[as_release_id] = await self.call('v1.setting.source.release.get_by_id',
# risotto_context,
# release_id=as_release_id)
await self.generate(risotto_context,
servermodel_name,
servermodel_id,
dependencies,
generate_cache)
sm_dict = {'servermodel_name': servermodel_name,
'servermodel_description': servermodel_description,
'servermodel_parents_id': servermodel_parents_id,
'servermodel_applicationservice_id': applicationservice_id,
'release_id': release_id,
'servermodel_id': servermodel_id}
return sm_dict
def parse_parents(self,
servermodels: Dict,
servermodel: Dict,
parents: List=None) -> List:
if parents is None:
parents = [servermodel['name']]
parent = servermodel['parent']
if parent in servermodels:
parents.append(parent)
self.parse_parents(servermodels, servermodels[parent], parents)
return parents
@register('v1.setting.applicationservice.updated')
async def applicationservice_updated(self,
risotto_context: Context,
applicationservice_id):
# FIXME applicationservices qui depend de ce services => updated
sql = '''
SELECT ServermodelId as servermodel_id, ServermodelName as servermodel_name, ServermodelDescription as servermodel_description, ServermodelParentsId as servermodel_parents_id, ServermodelReleaseId as release_id, ServermodelApplicationServiceId as servermodel_applicationservice_id
FROM Servermodel
WHERE ServermodelApplicationServiceId = $1
'''
servermodel = await risotto_context.connection.fetchrow(sql,
applicationservice_id)
if servermodel is not None:
servermodel_name = servermodel['servermodel_name']
servermodel_id = servermodel['servermodel_id']
release_id = servermodel['release_id']
applicationservice = await self.call('v1.setting.applicationservice.get_by_id',
risotto_context,
applicationservice_id=applicationservice_id)
dependencies = applicationservice['applicationservice_dependencies']
await self.generate(risotto_context,
servermodel_name,
servermodel_id,
dependencies,
None)
await self.publish('v1.setting.servermodel.updated',
risotto_context,
**servermodel)
@register('v1.setting.servermodel.dataset.updated')
async def servermodel_dataset_updated(self,
risotto_context: Context,
source_name: str,
release_distribution: int):
release = await self.call('v1.setting.source.release.describe',
risotto_context,
source_name=source_name,
release_distribution=release_distribution)
release_id = release['release_id']
generate_cache = {'applicationservice': {},
'release_id': {release['release_id']: (release['source_name'],
release['release_name'])}}
servermodel_path = join(self.source_root_path,
source_name,
release['release_name'],
'servermodel')
servermodels = {}
for servermodel in listdir(servermodel_path):
if not servermodel.endswith('.yml'):
continue
servermodel_description_path = join(servermodel_path, servermodel)
try:
with open(servermodel_description_path, 'r') as servermodel_yml:
servermodel_description = load(servermodel_yml,
Loader=SafeLoader)
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
raise ExecutionError(_(f'Error while reading {servermodel_description_path}: {err}'))
servermodels[servermodel_description['name']] = servermodel_description
servermodels[servermodel_description['name']]['done'] = False
for servermodel in servermodels.values():
if not servermodel['done']:
# parent needs to create before child, so retrieve all parents
parents = self.parse_parents(servermodels,
servermodel)
parents.reverse()
servermodel_parent = []
for new_servermodel in parents:
if not servermodels[new_servermodel]['done']:
servermodel_description = servermodels[new_servermodel]
parent = servermodel_description['parent']
if not servermodel_parent and parent is not None:
servermodel_parent = [await self._servermodel_describe(risotto_context,
parent,
release_id,
source_name,
release_distribution)]
# link application service with this servermodel
dependencies = []
for depend in servermodels[new_servermodel]['applicationservices']:
applicationservice = await self.call('v1.setting.applicationservice.describe',
risotto_context,
applicationservice_name=depend,
source_name=source_name,
release_distribution=release_distribution)
dependencies.append(applicationservice['applicationservice_id'])
sm_name = servermodel_description['name']
sm_description = servermodel_description['description']
try:
servermodel_ob = await self._servermodel_create(risotto_context,
sm_name,
sm_description,
servermodel_parent,
dependencies,
release_id,
generate_cache)
await self.publish('v1.setting.servermodel.created',
risotto_context,
**servermodel_ob)
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
raise ExecutionError(_(f"Error while injecting servermodel {sm_name} in database: {err}"))
servermodel_parent = [servermodel_ob]
servermodel_description['done'] = True
return {'retcode': 0, 'returns': _('Servermodels successfully loaded')}
@register('v1.setting.servermodel.list')
async def servermodel_list(self,
risotto_context: Context,
source_id: int):
sql = '''
SELECT ServermodelId as servermodel_id, ServermodelName as servermodel_name, ServermodelDescription as servermodel_description, ServermodelParentsId as servermodel_parents_id, ServermodelReleaseId as release_id, ServermodelApplicationServiceId as servermodel_applicationservice_id
FROM Servermodel
'''
servermodels = await risotto_context.connection.fetch(sql)
return [dict(r) for r in servermodels]
@register('v1.setting.servermodel.describe')
async def servermodel_describe(self,
risotto_context: Context,
servermodel_name,
source_name,
release_distribution) -> Dict:
release = await self.call('v1.setting.source.release.describe',
risotto_context,
source_name=source_name,
release_distribution=release_distribution)
return await self._servermodel_describe(risotto_context,
servermodel_name,
release['release_id'],
source_name,
release_distribution)
async def _servermodel_describe(self,
risotto_context,
servermodel_name,
release_id,
source_name,
release_distribution):
sql = '''
SELECT ServermodelId as servermodel_id, ServermodelName as servermodel_name, ServermodelDescription as servermodel_description, ServermodelParentsId as servermodel_parents_id, ServermodelReleaseId as release_id, ServermodelApplicationServiceId as servermodel_applicationservice_id
FROM Servermodel
WHERE ServermodelName=$1 AND ServermodelReleaseId=$2
'''
servermodel = await risotto_context.connection.fetchrow(sql,
servermodel_name,
release_id)
if not servermodel:
raise Exception(_(f'"{servermodel_name}" is not a valid name for a servermodel in source "{source_name}" and release "{release_distribution}"'))
return dict(servermodel)
@register('v1.setting.servermodel.create', notification='v1.setting.servermodel.created')
async def create_servermodel(self,
risotto_context: Context,
servermodel_name: str,
servermodel_description: str,
servermodel_parents_name: List[int],
servermodel_parents_source_name: str,
servermodel_parents_release_distribution: str) -> Dict:
release = await self.call('v1.setting.source.release.describe',
risotto_context,
source_name=servermodel_parents_source_name,
release_distribution=servermodel_parents_release_distribution)
release_id = release['release_id']
servermodel_parents = []
for servermodel_parent_name in servermodel_parents_name:
servermodel_parents.append(await self._servermodel_describe(risotto_context,
servermodel_parent_name,
release_id,
servermodel_parents_source_name,
servermodel_parents_release_distribution))
return await self._servermodel_create(risotto_context,
servermodel_name,
servermodel_description,
servermodel_parents,
[],
self.internal_release_id,
None)

View File

@ -1 +0,0 @@
from .session import Risotto

View File

@ -1,316 +0,0 @@
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
from ...config import get_config
class Risotto(Controller):
def __init__(self,
test):
self.modify_storage = Storage(engine='dictionary')
self.internal_source_name = get_config()['servermodel']['internal_source']
self.internal_distribution_name = get_config()['servermodel']['internal_distribution']
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.setting.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.setting.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.setting.session.servermodel.start')
async def start_session_servermodel(self,
risotto_context: Context,
servermodel_name: str) -> Dict:
""" start a new config session for a server or a servermodel
"""
config_module = dispatcher.get_service('config')
servermodel = await self.call('v1.setting.servermodel.describe',
risotto_context,
servermodel_name=servermodel_name,
source_name=self.internal_source_name,
release_distribution=self.internal_distribution_name)
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.setting.session.server.list', 'v1.setting.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.setting.session.server.filter', 'v1.setting.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.setting.session.server.configure', 'v1.setting.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.setting.session.server.validate', 'v1.setting.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.setting.session.server.get', 'v1.setting.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.setting.session.server.stop', 'v1.setting.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())
await 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')

View File

@ -1,147 +0,0 @@
import time
from typing import Dict
from tiramisu import Config
from rougail import modes
from ...error import CallError, NotAllowedError
class StorageError(Exception):
pass
class Storage(object):
__slots__ = ('sessions',)
def __init__(self):
self.sessions = {}
async def add_session(self,
session_id: int,
orig_config: Config,
server_id: int,
username: str,
config_storage):
prefix_id = f'{session_id}_'
config_name = self.get_config_name(server_id)
config_id = f'{prefix_id}{config_name}'
# copy Config and all it's parents
meta = await orig_config.config.deepcopy(session_id=config_id,
storage=config_storage,
metaconfig_prefix=prefix_id)
# retrieve the copied config (not metaconfig)
config = meta
while True:
try:
children = list(await config.config.list())
if not children:
# it's an empty metaconfig
break
config = children[0]
except:
# it's a config, so no "list" method
break
await config.property.read_write()
# set the default owner
await self.set_owner(config,
username)
# store it
self.sessions[session_id] = {'config': config,
# do not delete meta, so keep it!
'meta': meta,
'id': server_id,
'timestamp': int(time.time()),
'username': username}
await self.set_config_mode(session_id,
'normal')
await self.set_config_debug(session_id,
False)
self.set_namespace(session_id,
'creole')
async def set_config_mode(self,
id: int,
mode: str):
""" Define which edition mode to select
"""
config = self.sessions[id]['config']
for mode_level in modes.values():
if modes[mode] < mode_level:
await config.property.add(mode_level.name)
else:
await config.property.pop(mode_level.name)
self.sessions[id]['mode'] = mode
async def set_config_debug(self, id_, is_debug):
""" Enable/Disable debug mode
"""
config = self.sessions[id_]['config']
if is_debug:
await config.property.pop('hidden')
else:
await config.property.add('hidden')
self.sessions[id_]['debug'] = is_debug
def set_namespace(self,
session_id: int,
namespace: str):
self.sessions[session_id]['option'] = self.sessions[session_id]['config'].option(namespace)
self.sessions[session_id]['namespace'] = namespace
def get_sessions(self):
return self.sessions;
def get_session(self,
session_id: int,
username: str) -> Dict:
if session_id not in self.sessions:
raise Exception(f'the session "{session_id}" not exists')
session = self.sessions[session_id]
if username != session['username']:
raise NotAllowedError()
return session
async def del_session(self,
id: int):
config = self.sessions[id]['meta']
while True:
try:
children = list(await config.config.list())
if not children:
# it's an empty metaconfig
break
config = children[0]
await config.session.reset()
except:
# it's a config, so no "list" method
break
await self.sessions[id]['meta'].session.reset()
del self.sessions[id]
class StorageServer(Storage):
def get_config_name(self,
server_id: int):
return f'std_{server_id}'
async def set_owner(self,
config: Config,
username: str):
await config.owner.set(username)
class StorageServermodel(Storage):
def get_config_name(self,
server_id: int):
return f'v_{server_id}'
async def set_owner(self,
config: Config,
username: str):
await config.owner.set('servermodel_' + username)
storage_server = StorageServer()
storage_servermodel = StorageServermodel()

View File

@ -1 +0,0 @@
from .source import Risotto

View File

@ -1,150 +0,0 @@
from typing import Dict, List
from ...controller import Controller
from ...register import register
from ...context import Context
import requests
import yaml
import os
from ...utils import _
from ...config import get_config
class Risotto(Controller):
@register('v1.setting.source.create')
async def source_create(self,
risotto_context: Context,
source_name: str,
source_url: str) -> Dict:
source_upsert = """INSERT INTO Source(SourceName, SourceURL) VALUES ($1, $2)
ON CONFLICT (SourceName) DO UPDATE SET SourceURL = $2
RETURNING SourceId
"""
# If given url is not 'none' (a.k.a internal source)
# Look for file releases.yml at given url
# If such a file exists, consider source a valid one and create source in database.
if source_url != 'none':
try:
releases = yaml.load(requests.get(source_url.rstrip('/') + '/releases.yml').content, Loader=yaml.SafeLoader)
except requests.exceptions.ConnectionError as err:
raise Exception(_('Invalid URL'))
except yaml.scanner.ScannerError as err:
raise Exception(_('Invalid releases.yml file'))
except:
raise Exception(_('Invalid source'))
else:
releases = {'1.0.0': {'distribution': 'last'}}
os.makedirs(os.path.join(get_config().get('source').get('root_path'), source_name), exist_ok=True)
with open(os.path.join(get_config().get('source').get('root_path'), source_name, 'releases.yml'), 'w') as release_file:
yaml.dump(releases, release_file)
source_id = await risotto_context.connection.fetchval(source_upsert,
source_name,
source_url)
return {'source_name': source_name,
'source_url': source_url,
'source_id': source_id}
@register('v1.setting.source.describe')
async def source_describe(self,
risotto_context: Context,
source_name: str) -> Dict:
source_get = """SELECT SourceId as source_id, SourceName as source_name, SourceURL as source_url
FROM Source
WHERE SourceName = $1
"""
source = await risotto_context.connection.fetchrow(source_get,
source_name)
if not source:
raise Exception(_(f'unknown source with name {source_name}'))
return dict(source)
@register('v1.setting.source.list')
async def source_list(self,
risotto_context: Context) -> List[Dict]:
source_list = """SELECT SourceId as source_id, SourceName as source_name, SourceURL as source_url
FROM Source
"""
result = await risotto_context.connection.fetch(source_list)
return [dict(r) for r in result]
@register('v1.setting.source.dataset.update')
async def version_update(self,
risotto_context: Context,
source_id: int,
release_name: str):
# source.release.create is an upsert, do not using it
release_insert = """INSERT INTO Release(ReleaseName, ReleaseSourceId) VALUES ($1, $2)
RETURNING ReleaseId
"""
release_id = await risotto_context.connection.fetchval(release_insert,
release_name,
source_id)
return {'release_id': release_id,
'release_name': release_name}
@register('v1.setting.source.release.create')
async def source_release_create(self,
risotto_context: Context,
source_name: str,
release_name: str,
release_distribution: str) -> Dict:
source_get = """SELECT SourceId as source_id, SourceName as source_name, SourceURL as source_url
FROM Source
WHERE SourceName = $1
"""
release_upsert = """INSERT INTO Release(ReleaseName, ReleaseSourceId, ReleaseDistribution) VALUES ($1, $2, $3)
ON CONFLICT (ReleaseName, ReleaseSourceId) DO UPDATE SET ReleaseName = $1
RETURNING ReleaseId
"""
source_obj = await risotto_context.connection.fetchrow(source_get,
source_name)
if not source_obj:
raise Exception(_(f'unable to find a source with name {source_name}'))
source = dict(source_obj)
release_id = await risotto_context.connection.fetchval(release_upsert,
release_name,
source['source_id'],
release_distribution)
del source['source_id']
source['release_id'] = release_id
source['release_name'] = release_name
source['release_distribution'] = release_distribution
return source
@register('v1.setting.source.release.list')
async def release_list(self,
risotto_context,
source_name: str) -> Dict:
release_query = """SELECT ReleaseId as release_id, SourceName as source_name, SourceURL as source_url, ReleaseName as release_name, ReleaseDistribution as release_distribution
FROM Release, Source
WHERE Source.SourceName=$1 AND Source.SourceId=Release.ReleaseSourceId"""
result = await risotto_context.connection.fetch(release_query,
source_name)
return [dict(r) for r in result]
@register('v1.setting.source.release.describe')
async def release_describe(self,
risotto_context,
source_name: str,
release_distribution: str) -> Dict:
release_query = """SELECT ReleaseId as release_id, SourceName as source_name, SourceURL as source_url, ReleaseName as release_name, ReleaseDistribution as release_distribution
FROM Release, Source
WHERE Source.SourceName=$1 AND Source.SourceId=Release.ReleaseSourceId AND Release.ReleaseDistribution=$2"""
result = await risotto_context.connection.fetchrow(release_query,
source_name,
release_distribution)
if not result:
raise Exception(_(f'unknown release distribution {release_distribution} in source {source_name}'))
return dict(result)
@register('v1.setting.source.release.get_by_id')
async def release_get_by_id(self,
risotto_context: Context,
release_id: int) -> Dict:
release_query = """SELECT ReleaseId as release_id, SourceName as source_name, SourceURL as source_url, ReleaseName as release_name, ReleaseDistribution as release_distribution
FROM Release, Source
WHERE Release.ReleaseId = $1 AND Source.SourceId = Release.ReleaseSourceId"""
result = await risotto_context.connection.fetchrow(release_query,
release_id)
if not result:
raise Exception(_(f'unknown release id {release_id}'))
return dict(result)

View File

@ -1 +0,0 @@
from .template import Risotto

View File

@ -1,66 +0,0 @@
from os import mkdir
from os.path import isdir, join
from shutil import rmtree
from typing import Dict
from rougail.template import generate
from tiramisu import Storage
from ...config import CONFIGURATION_DIR, TMP_DIR, get_config
from ...controller import Controller
from ...register import register
from ...dispatcher import dispatcher
from ...utils import _
class Risotto(Controller):
def __init__(self,
test: bool) -> None:
self.storage = Storage(engine='dictionary')
self.cache_root_path = join(get_config().get('cache').get('root_path'), 'servermodel')
@register('v1.setting.template.generate')
async def template_get(self,
risotto_context,
server_name: str) -> Dict:
# get informations for server
server = await self.call('v1.setting.server.describe',
risotto_context,
server_name=server_name)
server_id = server['server_id']
servermodel_id = server['server_servermodel_id']
# verify if server has deployed configuration
config_module = dispatcher.get_service('config')
server = config_module.server[server_id]
export = await server['server'].value.exportation()
if not export[0]:
raise Exception(_(f'configuration for server "{server_name}" is empty, you should deploy it first'))
# copy deployed configuration
async with await server['server'].config.deepcopy(storage=self.storage) as config:
meta = config
while True:
try:
children = list(await config.config.list())
except:
break
if children:
config = children[0]
else:
break
configurations_dir = join(CONFIGURATION_DIR,
str(server_id))
if isdir(configurations_dir):
rmtree(configurations_dir)
mkdir(configurations_dir)
tmp_dir = join(TMP_DIR, str(server_id))
if isdir(tmp_dir):
rmtree(tmp_dir)
mkdir(tmp_dir)
templates_dir = join(self.cache_root_path, str(servermodel_id), 'templates')
await generate(config,
server['funcs_file'],
templates_dir,
tmp_dir,
configurations_dir)
del meta, config
# FIXME del session !
return {'server_name': server_name,
'template_dir': configurations_dir}

View File

@ -1 +0,0 @@
from .uri import Risotto

View File

@ -1,117 +0,0 @@
from typing import Dict, List
from ...controller import Controller
from ...register import register
from ...context import Context
from ...utils import _
class Risotto(Controller):
async def on_join(self,
risotto_context):
for uri in ['v1.setting.applicationservice.create',
'v1.setting.applicationservice.dataset.updated',
'v1.setting.applicationservice.dependency.add',
'v1.setting.server.create',
'v1.setting.servermodel.create',
'v1.setting.servermodel.dataset.updated',
'v1.setting.session.server.start',
'v1.setting.source.create',
'v1.setting.source.dataset.update',
'v1.setting.source.release.create',
'v1.setting.template.generate',
'v1.setting.uri.role.join',
'v1.setting.uri.role.list',
'v1.setting.user.create',
'v1.setting.user.delete',
'v1.setting.user.list',
'v1.setting.user.role.create',
'v1.setting.config.configuration.server.get',
'v1.setting.user.role.list']:
try:
await self._uri_role_join(risotto_context,
role_name='administrator',
uri_name=uri)
except:
pass
for uri in ['v1.setting.applicationservice.describe',
'v1.setting.server.describe',
'v1.setting.server.list',
'v1.setting.servermodel.list',
'v1.setting.session.server.configure',
'v1.setting.session.server.filter',
'v1.setting.session.server.get',
'v1.setting.session.server.list',
'v1.setting.session.servermodel.configure',
'v1.setting.session.servermodel.filter',
'v1.setting.session.servermodel.get',
'v1.setting.session.servermodel.list',
'v1.setting.session.servermodel.start',
'v1.setting.session.servermodel.stop',
'v1.setting.session.servermodel.validate',
'v1.setting.session.server.stop',
'v1.setting.session.server.validate',
'v1.setting.source.describe',
'v1.setting.source.list',
'v1.setting.source.release.list']:
try:
await self._uri_role_join(risotto_context,
role_name='all',
uri_name=uri)
except:
pass
for uri in ['v1.setting.server.describe',
'v1.setting.applicationservice.dependency.add',
'v1.setting.config.configuration.server.get',
'v1.setting.config.configuration.server.deploy',
'v1.setting.session.server.start',
'v1.setting.template.generate']:
try:
await self._uri_role_join(risotto_context,
role_name='server_rw',
uri_name=uri)
except:
pass
@register('v1.setting.uri.role.join')
async def uri_role_join(self,
risotto_context: Context,
role_name: str,
uri_name: str) -> Dict:
return await self._uri_role_join(risotto_context,
role_name,
uri_name)
async def _uri_role_join(self,
risotto_context: Context,
role_name: str,
uri_name: str) -> Dict:
# Verify if user exists and get ID
sql = '''
SELECT URIId
FROM URI
WHERE URIName = $1
'''
uri_id = await risotto_context.connection.fetchval(sql,
uri_name)
if uri_id is None:
raise Exception(_(f'unable to find message {uri_name}'))
sql = '''
INSERT INTO RoleURI(RoleName, URIId)
VALUES ($1,$2)
ON CONFLICT DO NOTHING
'''
uri_id = await risotto_context.connection.fetchrow(sql,
role_name,
uri_id)
return {'role_name': role_name,
'uri_name': uri_name}
@register('v1.setting.uri.role.list')
async def uri_role_list(self,
risotto_context: Context) -> List[Dict]:
sql = '''
SELECT RoleName as role_name, URI.URIName as uri_name
FROM RoleURI, URI
WHERE RoleURI.URIId = URI.URIId
'''
return [dict(r) for r in await risotto_context.connection.fetch(sql)]

View File

@ -1 +0,0 @@
from .user import Risotto

View File

@ -1,211 +0,0 @@
from typing import Dict, Optional
from ...controller import Controller
from ...register import register
from ...context import Context
from ...utils import _
from ...config import get_config
class Risotto(Controller):
async def on_join(self,
risotto_context: Context) -> None:
""" pre-load servermodel and server
"""
user_login = get_config()['global']['admin_user']
sql = '''
SELECT UserId
FROM RisottoUser
WHERE UserLogin = $1
'''
if await risotto_context.connection.fetchval(sql,
user_login) is None:
await self._user_create(risotto_context,
user_login,
user_login,
user_login)
await self._user_role_create(risotto_context,
user_login,
'administrator',
None,
None)
async def _user_create(self,
risotto_context: Context,
user_login: str,
user_name: str,
user_surname: str) -> Dict:
user_insert = """INSERT INTO RisottoUser(UserLogin, UserName, UserSurname)
VALUES ($1,$2,$3)
RETURNING UserId
"""
user_id = await risotto_context.connection.fetchval(user_insert,
user_login,
user_name,
user_surname)
await self.call('v1.setting.user.role.create',
risotto_context,
user_login=user_login,
role_name='all')
return {'user_id': user_id,
'user_login': user_login,
'user_name': user_name,
'user_surname': user_surname}
@register('v1.setting.user.create')
async def user_create(self,
risotto_context: Context,
user_login: str,
user_name: str,
user_surname: str) -> Dict:
return await self._user_create(risotto_context,
user_login,
user_name,
user_surname)
@register('v1.setting.user.list')
async def user_list(self,
risotto_context: Context) -> Dict:
sql = '''
SELECT UserId as user_id, UserLogin as user_login, UserName as user_name, UserSurname as user_surname
FROM RisottoUser
'''
users = await risotto_context.connection.fetch(sql)
return [dict(r) for r in users]
@register('v1.setting.user.delete')
async def user_delete(self,
risotto_context: Context,
user_login: str) -> Dict:
sql = '''
DELETE FROM RisottoUser
WHERE UserLogin = $1
RETURNING UserId as user_id, UserLogin as user_login, UserName as user_name, UserSurname as user_surname
'''
user = await risotto_context.connection.fetchrow(sql,
user_login)
if user is None:
raise Exception(_(f'unable to find user {user_login}'))
return dict(user)
async def _user_role_create(self,
risotto_context: Context,
user_login: str,
role_name: str,
role_attribute: str,
role_attribute_value: str) -> Dict:
# Verify if user exists and get ID
sql = '''
SELECT UserId
FROM RisottoUser
WHERE UserLogin = $1
'''
user_id = await risotto_context.connection.fetchval(sql,
user_login)
if user_id is None:
raise Exception(_(f'unable to find user {user_login}'))
if role_attribute == role_attribute_value == None:
sql = '''SELECT RoleId
FROM UserRole
WHERE RoleUserId = $1 AND RoleName = $2
'''
role_id = await risotto_context.connection.fetchval(sql,
user_id,
role_name)
else:
sql = '''SELECT RoleId
FROM UserRole
WHERE RoleUserId = $1 AND RoleName = $2 AND RoleAttribute = $3 AND RoleAttributeValue = $4
'''
role_id = await risotto_context.connection.fetchval(sql,
user_id,
role_name,
role_attribute,
role_attribute_value)
if role_id is None:
sql = '''INSERT INTO UserRole(RoleUserId, RoleName, RoleAttribute, RoleAttributeValue)
VALUES($1,$2,$3,$4)
RETURNING RoleId
'''
role_id = await risotto_context.connection.fetchval(sql,
user_id,
role_name,
role_attribute,
role_attribute_value)
return {'role_id': role_id,
'user_login': user_login,
'role_name': role_name,
'role_attribute': role_attribute,
'role_attribute_value': role_attribute_value}
@register('v1.setting.user.role.create')
async def user_role_create(self,
risotto_context: Context,
user_login: str,
role_name: str,
role_attribute: str,
role_attribute_value: str) -> Dict:
return await self._user_role_create(risotto_context,
user_login,
role_name,
role_attribute,
role_attribute_value)
@register('v1.setting.user.role.list')
async def user_role_list(self,
risotto_context: Context,
user_login: Optional[str]) -> Dict:
if not user_login:
sql = '''
SELECT RoleId as role_id, RoleName as role_name, RoleAttribute as role_attribute, RoleAttributeValue as role_attribute_value, RisottoUser.UserLogin as user_login
FROM UserRole, RisottoUser
WHERE UserRole.RoleUserId = RisottoUser.UserId
'''
roles = await risotto_context.connection.fetch(sql)
else:
# Verify if user exists and get ID
sql = '''
SELECT UserId
FROM RisottoUser
WHERE UserLogin = $1
'''
user_id = await risotto_context.connection.fetchval(sql,
user_login)
if user_id is None:
raise Exception(_(f'unable to find user {user_login}'))
sql = '''
SELECT RoleId as role_id, RoleName as role_name, RoleAttribute as role_attribute, RoleAttributeValue as role_attribute_value, RisottoUser.UserLogin as user_login
FROM UserRole, RisottoUser
WHERE UserRole.RoleUserId = RisottoUser.UserId AND UserRole.RoleUserId = $1
'''
roles = await risotto_context.connection.fetch(sql,
user_id)
return [dict(r) for r in roles]
#
# FIXME comment savoir quel role il faut supprimer ? avec attribut ou juste l'ID ?
# @register('v1.setting.user.role.delete')
# async def user_role_delete(self,
# risotto_context: Context,
# user_login: str,
# role_name: str) -> Dict:
# # Verify if user exists and get ID
# sql = '''
# SELECT UserId
# FROM RisottoUser
# WHERE UserLogin = $1
# '''
# user_id = await risotto_context.connection.fetchval(sql,
# user_login)
# if user_id is None:
# raise Exception(_(f'unable to find user {user_login}'))
# sql = '''
# DELETE FROM RisottoRole
# WHERE RoleName = $1 AND UserId = $2
# RETURNING RoleId as role_id, RoleName as role_name, RoleAttribute as role_attribute, RoleAttributeValue as role_attribute_value
# '''
# role = await risotto_context.connection.fetchrow(sql,
# role_name,
# user_id)
# if role is None:
# raise Exception(_(f'unable to find role {role_name}'))
# return dict(role)