Compare commits

..

15 Commits

Author SHA1 Message Date
98c77bf719 Merge branch 'develop' into dist/risotto/risotto-2.8.0/develop 2020-09-20 21:33:15 +02:00
1b9d87fa53 Merge branch 'develop' into dist/risotto/risotto-2.8.0/develop 2020-09-19 10:33:34 +02:00
0e988d7040 Merge branch 'develop' into dist/risotto/risotto-2.8.0/develop 2020-09-19 09:20:04 +02:00
be97d757d9 Merge branch 'develop' into dist/risotto/risotto-2.8.0/develop 2020-09-16 17:38:04 +02:00
19d90fd9bc Delete changelog 2020-09-16 10:39:37 +02:00
5653de1e99 Merge branch 'develop' into dist/risotto/risotto-2.8.0/develop 2020-09-16 08:16:33 +02:00
399bfb9ab6 add sql in risotto package 2020-09-06 09:47:14 +02:00
234b82b459 Merge branch 'develop' into dist/risotto/risotto-2.8.0/develop 2020-08-12 15:09:49 +02:00
c9e0bcbbfe update dependencies 2020-08-12 15:09:20 +02:00
47e4976f54 install python3-risotto 2020-08-12 11:09:45 +02:00
dd33ea5b8f add dh-python build dependency 2020-08-12 10:59:26 +02:00
689df4ec23 Merge branch 'develop' into dist/risotto/risotto-2.8.0/develop 2020-08-12 10:48:59 +02:00
223fb9aaf3 separate risotto and python3-risotto package 2020-08-12 10:48:12 +02:00
bed27a1e58 separate risotto and python3-risotto package 2020-08-12 10:44:59 +02:00
40eff91684 ajout de la dependance vers asyncpg 2020-08-12 08:41:15 +02:00
16 changed files with 228 additions and 1243 deletions

5
debian/changelog vendored
View File

@ -1,5 +0,0 @@
risotto (0.1) unstable; urgency=low
* first version
-- Cadoles <contact@cadoles.com> Fri, 20 Mar 2020 15:18:25 +0100

10
debian/control vendored
View File

@ -2,13 +2,19 @@ Source: risotto
Section: admin Section: admin
Priority: extra Priority: extra
Maintainer: Cadoles <contact@cadoles.com> Maintainer: Cadoles <contact@cadoles.com>
Build-depends: debhelper (>=11), python3-all, python3-setuptools Build-depends: debhelper (>=11), python3-all, python3-setuptools, dh-python
Standards-Version: 3.9.4 Standards-Version: 3.9.4
Homepage: https://forge.cadoles.com/Infra/risotto Homepage: https://forge.cadoles.com/Infra/risotto
Package: python3-risotto
Architecture: any
Pre-Depends: dpkg, python3, ${misc:Pre-Depends}
Depends: ${python:Depends}, ${misc:Depends}, python3-asyncpg, python3-rougail, python3-aiohttp
Description: configuration manager libraries
Package: risotto Package: risotto
Architecture: any Architecture: any
Pre-Depends: dpkg, python3, ${misc:Pre-Depends} Pre-Depends: dpkg, python3, ${misc:Pre-Depends}
Depends: ${python:Depends}, ${misc:Depends} Depends: ${python:Depends}, ${misc:Depends}, python3-risotto
Description: configuration manager Description: configuration manager

2
debian/risotto.install vendored Normal file
View File

@ -0,0 +1,2 @@
script/risotto-server usr/bin/
sql/risotto.sql usr/share/eole/db/eole-risotto/gen/

5
script/risotto-server Normal file → Executable file
View File

@ -1,16 +1,13 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from sdnotify import SystemdNotifier
from asyncio import get_event_loop from asyncio import get_event_loop
from risotto import get_app from risotto import get_app
if __name__ == '__main__': if __name__ == '__main__':
notifier = SystemdNotifier()
loop = get_event_loop() loop = get_event_loop()
loop.run_until_complete(get_app(loop)) loop.run_until_complete(get_app(loop))
print('HTTP server ready')
notifier.notify("READY=1")
try: try:
print('HTTP server ready')
loop.run_forever() loop.run_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass

View File

@ -1,16 +1,8 @@
CREATE TABLE RisottoLog( CREATE TABLE log(
LogId SERIAL PRIMARY KEY,
ContextId INTEGER,
Msg VARCHAR(255) NOT NULL, Msg VARCHAR(255) NOT NULL,
URI VARCHAR(255), Level VARCHAR(10) NOT NULL,
URIS VARCHAR(255), Path VARCHAR(255),
UserLogin VARCHAR(100) NOT NULL, Username VARCHAR(100) NOT NULL,
Status INTEGER NOT NULL, Data JSON,
Kwargs JSON, Date timestamp DEFAULT current_timestamp
Returns JSON,
StartDate timestamp DEFAULT current_timestamp,
StopDate timestamp
); );
CREATE INDEX RisottoLog_ContextId_index ON RisottoLog(ContextId);
CREATE INDEX RisottoLog_Login_index ON RisottoLog(UserLogin);
CREATE INDEX RisottoLog_URI_index ON RisottoLog(URI);

View File

@ -1,7 +1,6 @@
from os import environ from os import environ
from os.path import isfile from os.path import isfile
from configobj import ConfigObj from configobj import ConfigObj
from uuid import uuid4
CONFIG_FILE = environ.get('CONFIG_FILE', '/etc/risotto/risotto.conf') CONFIG_FILE = environ.get('CONFIG_FILE', '/etc/risotto/risotto.conf')
@ -17,10 +16,6 @@ if 'RISOTTO_PORT' in environ:
RISOTTO_PORT = environ['RISOTTO_PORT'] RISOTTO_PORT = environ['RISOTTO_PORT']
else: else:
RISOTTO_PORT = config.get('RISOTTO_PORT', 8080) RISOTTO_PORT = config.get('RISOTTO_PORT', 8080)
if 'RISOTTO_URL' in environ:
RISOTTO_URL = environ['RISOTTO_URL']
else:
RISOTTO_URL = config.get('RISOTTO_URL', 'http://localhost:8080/')
if 'CONFIGURATION_DIR' in environ: if 'CONFIGURATION_DIR' in environ:
CONFIGURATION_DIR = environ['CONFIGURATION_DIR'] CONFIGURATION_DIR = environ['CONFIGURATION_DIR']
else: else:
@ -65,18 +60,6 @@ if 'CELERYRISOTTO_DB_USER' in environ:
CELERYRISOTTO_DB_USER = environ['CELERYRISOTTO_DB_USER'] CELERYRISOTTO_DB_USER = environ['CELERYRISOTTO_DB_USER']
else: else:
CELERYRISOTTO_DB_USER = config.get('CELERYRISOTTO_DB_USER', None) CELERYRISOTTO_DB_USER = config.get('CELERYRISOTTO_DB_USER', None)
if 'LEMUR_DB_NAME' in environ:
LEMUR_DB_NAME = environ['LEMUR_DB_NAME']
else:
LEMUR_DB_NAME = config.get('LEMUR_DB_NAME', None)
if 'LEMUR_DB_PASSWORD' in environ:
LEMUR_DB_PASSWORD = environ['LEMUR_DB_PASSWORD']
else:
LEMUR_DB_PASSWORD = config.get('LEMUR_DB_PASSWORD', None)
if 'LEMUR_DB_USER' in environ:
LEMUR_DB_USER = environ['LEMUR_DB_USER']
else:
LEMUR_DB_USER = config.get('LEMUR_DB_USER', None)
if 'DB_ADDRESS' in environ: if 'DB_ADDRESS' in environ:
DB_ADDRESS = environ['DB_ADDRESS'] DB_ADDRESS = environ['DB_ADDRESS']
else: else:
@ -105,45 +88,6 @@ if 'IMAGE_PATH' in environ:
IMAGE_PATH = environ['IMAGE_PATH'] IMAGE_PATH = environ['IMAGE_PATH']
else: else:
IMAGE_PATH = config.get('IMAGE_PATH', '/tmp') IMAGE_PATH = config.get('IMAGE_PATH', '/tmp')
if 'PASSWORD_ADMIN_USERNAME' in environ:
PASSWORD_ADMIN_USERNAME = environ['PASSWORD_ADMIN_USERNAME']
else:
PASSWORD_ADMIN_USERNAME = config.get('PASSWORD_ADMIN_USERNAME', 'risotto')
if 'PASSWORD_ADMIN_EMAIL' in environ:
PASSWORD_ADMIN_EMAIL = environ['PASSWORD_ADMIN_EMAIL']
else:
# this parameter is mandatory
PASSWORD_ADMIN_EMAIL = config.get('PASSWORD_ADMIN_EMAIL', 'XXX')
if 'PASSWORD_ADMIN_PASSWORD' in environ:
PASSWORD_ADMIN_PASSWORD = environ['PASSWORD_ADMIN_PASSWORD']
else:
# this parameter is mandatory
PASSWORD_ADMIN_PASSWORD = config.get('PASSWORD_ADMIN_PASSWORD', 'XXX')
if 'PASSWORD_DEVICE_IDENTIFIER' in environ:
PASSWORD_DEVICE_IDENTIFIER = environ['PASSWORD_DEVICE_IDENTIFIER']
else:
PASSWORD_DEVICE_IDENTIFIER = config.get('PASSWORD_DEVICE_IDENTIFIER', uuid4())
if 'PASSWORD_URL' in environ:
PASSWORD_URL = environ['PASSWORD_URL']
else:
PASSWORD_URL = config.get('PASSWORD_URL', 'https://localhost:8001/')
if 'PASSWORD_LENGTH' in environ:
PASSWORD_LENGTH = int(environ['PASSWORD_LENGTH'])
else:
PASSWORD_LENGTH = int(config.get('PASSWORD_LENGTH', 20))
if 'PKI_ADMIN_PASSWORD' in environ:
PKI_ADMIN_PASSWORD = environ['PKI_ADMIN_PASSWORD']
else:
PKI_ADMIN_PASSWORD = config.get('PKI_ADMIN_PASSWORD', 'XXX')
if 'PKI_ADMIN_EMAIL' in environ:
PKI_ADMIN_EMAIL = environ['PKI_ADMIN_EMAIL']
else:
PKI_ADMIN_EMAIL = config.get('PKI_ADMIN_EMAIL', 'XXX')
if 'PKI_URL' in environ:
PKI_URL = environ['PKI_URL']
else:
PKI_URL = config.get('PKI_URL', 'http://localhost:8002')
def dsn_factory(database, user, password, address=DB_ADDRESS): def dsn_factory(database, user, password, address=DB_ADDRESS):
@ -153,12 +97,10 @@ def dsn_factory(database, user, password, address=DB_ADDRESS):
_config = {'database': {'dsn': dsn_factory(RISOTTO_DB_NAME, RISOTTO_DB_USER, RISOTTO_DB_PASSWORD), _config = {'database': {'dsn': dsn_factory(RISOTTO_DB_NAME, RISOTTO_DB_USER, RISOTTO_DB_PASSWORD),
'tiramisu_dsn': dsn_factory(TIRAMISU_DB_NAME, TIRAMISU_DB_USER, TIRAMISU_DB_PASSWORD), 'tiramisu_dsn': dsn_factory(TIRAMISU_DB_NAME, TIRAMISU_DB_USER, TIRAMISU_DB_PASSWORD),
'celery_dsn': dsn_factory(CELERYRISOTTO_DB_NAME, CELERYRISOTTO_DB_USER, CELERYRISOTTO_DB_PASSWORD), 'celery_dsn': dsn_factory(CELERYRISOTTO_DB_NAME, CELERYRISOTTO_DB_USER, CELERYRISOTTO_DB_PASSWORD)
'lemur_dns': dsn_factory(LEMUR_DB_NAME, LEMUR_DB_USER, LEMUR_DB_PASSWORD),
}, },
'http_server': {'port': RISOTTO_PORT, 'http_server': {'port': RISOTTO_PORT,
'default_user': DEFAULT_USER, 'default_user': DEFAULT_USER},
'url': RISOTTO_URL},
'global': {'message_root_path': MESSAGE_PATH, 'global': {'message_root_path': MESSAGE_PATH,
'configurations_dir': CONFIGURATION_DIR, 'configurations_dir': CONFIGURATION_DIR,
'debug': True, 'debug': True,
@ -168,17 +110,6 @@ _config = {'database': {'dsn': dsn_factory(RISOTTO_DB_NAME, RISOTTO_DB_USER, RIS
'sql_dir': SQL_DIR, 'sql_dir': SQL_DIR,
'tmp_dir': TMP_DIR, 'tmp_dir': TMP_DIR,
}, },
'password': {'admin_username': PASSWORD_ADMIN_USERNAME,
'admin_email': PASSWORD_ADMIN_EMAIL,
'admin_password': PASSWORD_ADMIN_PASSWORD,
'device_identifier': PASSWORD_DEVICE_IDENTIFIER,
'service_url': PASSWORD_URL,
'length': PASSWORD_LENGTH,
},
'pki': {'admin_password': PKI_ADMIN_PASSWORD,
'owner': PKI_ADMIN_EMAIL,
'url': PKI_URL,
},
'cache': {'root_path': CACHE_ROOT_PATH}, 'cache': {'root_path': CACHE_ROOT_PATH},
'servermodel': {'internal_source_path': SRV_SEED_PATH, 'servermodel': {'internal_source_path': SRV_SEED_PATH,
'internal_source': 'internal'}, 'internal_source': 'internal'},

View File

@ -1,13 +1,3 @@
class Context: class Context:
def __init__(self): def __init__(self):
self.paths = [] self.paths = []
self.context_id = None
self.start_id = None
def copy(self):
context = Context()
for key, value in self.__dict__.items():
if key.startswith('__'):
continue
setattr(context, key, value)
return context

View File

@ -1,22 +1,6 @@
from os import listdir, makedirs from .dispatcher import dispatcher
from os.path import join, isdir, isfile
from shutil import rmtree
from traceback import print_exc
from typing import Dict
from rougail import RougailConvert, RougailConfig, RougailUpgrade
try:
from tiramisu3 import Storage, Config
except:
from tiramisu import Storage, Config
from .config import get_config
from .utils import _, tiramisu_display_name
from .logger import log
from .dispatcher import get_dispatcher
from .context import Context from .context import Context
from .utils import _
RougailConfig['variable_namespace'] = 'configuration'
class Controller: class Controller:
@ -24,8 +8,8 @@ class Controller:
""" """
def __init__(self, def __init__(self,
test: bool, test: bool,
) -> None: ):
self.dispatcher = get_dispatcher() pass
async def call(self, async def call(self,
uri: str, uri: str,
@ -42,11 +26,11 @@ class Controller:
module = message.split('.', 1)[0] module = message.split('.', 1)[0]
if current_module != module: if current_module != module:
raise ValueError(_(f'cannot call to external module ("{module}") to the URI "{uri}" from "{current_module}"')) raise ValueError(_(f'cannot call to external module ("{module}") to the URI "{uri}" from "{current_module}"'))
return await self.dispatcher.call(version, return await dispatcher.call(version,
message, message,
risotto_context, risotto_context,
**kwargs, **kwargs,
) )
async def publish(self, async def publish(self,
uri: str, uri: str,
@ -58,285 +42,13 @@ class Controller:
if args: if args:
raise ValueError(_(f'the URI "{uri}" can only be published with keyword arguments')) raise ValueError(_(f'the URI "{uri}" can only be published with keyword arguments'))
version, message = uri.split('.', 1) version, message = uri.split('.', 1)
await self.dispatcher.publish(version, await dispatcher.publish(version,
message, message,
risotto_context, risotto_context,
**kwargs, **kwargs,
) )
async def check_role(self,
uri: str,
username: str,
**kwargs: dict,
) -> None:
# create a new config
async with await Config(self.dispatcher.option) as config:
await config.property.read_write()
await config.option('message').value.set(uri)
subconfig = config.option(uri)
for key, value in kwargs.items():
try:
await subconfig.option(key).value.set(value)
except AttributeError:
if get_config()['global']['debug']:
print_exc()
raise ValueError(_(f'unknown parameter in "{uri}": "{key}"'))
except ValueOptionError as err:
raise ValueError(_(f'invalid parameter in "{uri}": {err}'))
await self.dispatcher.check_role(subconfig,
username,
uri,
)
async def on_join(self, async def on_join(self,
risotto_context, risotto_context,
): ):
pass pass
class TiramisuController(Controller):
def __init__(self,
test: bool,
) -> None:
self.source_imported = None
if not 'dataset_name' in vars(self):
raise Exception(f'please specify "dataset_name" to "{self.__class__.__name__}"')
self.tiramisu_cache_root_path = join(get_config()['cache']['root_path'], self.dataset_name)
super().__init__(test)
self.internal_source_name = get_config()['servermodel']['internal_source']
if not test:
db_conf = get_config()['database']['tiramisu_dsn']
self.save_storage = Storage(engine='postgres')
self.save_storage.setting(dsn=db_conf)
if self.dataset_name != 'servermodel':
self.optiondescription = None
self.dispatcher.set_function('v1.setting.dataset.updated',
None,
TiramisuController.dataset_updated,
self.__class__.__module__,
)
async def on_join(self,
risotto_context: Context,
) -> None:
if isdir(self.tiramisu_cache_root_path):
await self.load_datas(risotto_context)
async def dataset_updated(self,
risotto_context: Context,
) -> Dict:
await self.gen_dictionaries(risotto_context)
await self.load_datas(risotto_context)
async def gen_dictionaries(self,
risotto_context: Context,
) -> None:
sources = await self.get_sources(risotto_context)
source_imported = sources != [self.internal_source_name]
if source_imported and self.source_imported is False:
await self.load_datas(risotto_context)
self.source_imported = source_imported
if not self.source_imported:
return
self._aggregate_tiramisu_funcs(sources)
self._convert_dictionaries_to_tiramisu(sources)
async def get_sources(self,
risotto_context: Context,
) -> None:
return await self.call('v1.setting.source.list',
risotto_context,
)
def _aggregate_tiramisu_funcs(self,
sources: list,
) -> None:
dest_file = join(self.tiramisu_cache_root_path, 'funcs.py')
if not isdir(self.tiramisu_cache_root_path):
makedirs(self.tiramisu_cache_root_path)
with open(dest_file, 'wb') as funcs:
funcs.write(b"""try:
from tiramisu3 import valid_network_netmask, valid_ip_netmask, valid_broadcast, valid_in_network, valid_not_equal as valid_differ, valid_not_equal, calc_value
except:
from tiramisu import valid_network_netmask, valid_ip_netmask, valid_broadcast, valid_in_network, valid_not_equal as valid_differ, valid_not_equal, calc_value
""")
for source in sources:
root_path = join(source['source_directory'],
self.dataset_name,
)
if not isdir(root_path):
continue
for service in listdir(root_path):
path = join(root_path,
service,
'funcs',
)
if not isdir(path):
continue
for filename in listdir(path):
if not filename.endswith('.py'):
continue
filename_path = join(path, filename)
with open(filename_path, 'rb') as fh:
funcs.write(f'# {filename_path}\n'.encode())
funcs.write(fh.read())
funcs.write(b'\n')
def _convert_dictionaries_to_tiramisu(self,
sources: list,
) -> None:
funcs_file = join(self.tiramisu_cache_root_path, 'funcs.py')
tiramisu_file = join(self.tiramisu_cache_root_path, 'tiramisu.py')
dictionaries_dir = join(self.tiramisu_cache_root_path, 'dictionaries')
extras_dictionaries_dir = join(self.tiramisu_cache_root_path, 'extra_dictionaries')
if isdir(dictionaries_dir):
rmtree(dictionaries_dir)
makedirs(dictionaries_dir)
if isdir(extras_dictionaries_dir):
rmtree(extras_dictionaries_dir)
makedirs(extras_dictionaries_dir)
extras = []
upgrade = RougailUpgrade()
for source in sources:
root_path = join(source['source_directory'],
self.dataset_name,
)
if not isdir(root_path):
continue
for service in listdir(root_path):
# upgrade dictionaries
path = join(root_path,
service,
'dictionaries',
)
if not isdir(path):
continue
upgrade.load_xml_from_folders(path,
dictionaries_dir,
RougailConfig['variable_namespace'],
)
for service in listdir(root_path):
# upgrade extra dictionaries
path = join(root_path,
service,
'extras',
)
if not isdir(path):
continue
for namespace in listdir(path):
extra_dir = join(path,
namespace,
)
if not isdir(extra_dir):
continue
extra_dictionaries_dir = join(extras_dictionaries_dir,
namespace,
)
if not isdir(extra_dictionaries_dir):
makedirs(extra_dictionaries_dir)
extras.append((namespace, [extra_dictionaries_dir]))
upgrade.load_xml_from_folders(extra_dir,
extra_dictionaries_dir,
namespace,
)
del upgrade
config = RougailConfig.copy()
config['functions_file'] = funcs_file
config['dictionaries_dir'] = [dictionaries_dir]
config['extra_dictionaries'] = {}
for extra in extras:
config['extra_dictionaries'][extra[0]] = extra[1]
eolobj = RougailConvert(rougailconfig=config)
eolobj.save(tiramisu_file)
async def load(self,
risotto_context: Context,
name: str,
to_deploy: bool=False,
) -> Config:
if self.optiondescription is None:
# use file in cache
tiramisu_file = join(self.tiramisu_cache_root_path, 'tiramisu.py')
if not isfile(tiramisu_file):
raise Exception(_(f'unable to load the "{self.dataset_name}" configuration, is dataset loaded?'))
with open(tiramisu_file) as fileio:
tiramisu_locals = {}
try:
exec(fileio.read(), None, tiramisu_locals)
except Exception as err:
raise Exception(_(f'unable to load tiramisu file {tiramisu_file}: {err}'))
self.optiondescription = tiramisu_locals['option_0']
del tiramisu_locals
try:
letter = self.dataset_name[0]
if not to_deploy:
session_id = f'{letter}_{name}'
else:
session_id = f'{letter}td_{name}'
config = await Config(self.optiondescription,
session_id=session_id,
storage=self.save_storage,
display_name=tiramisu_display_name,
)
# change default rights
await config.property.read_only()
await config.permissive.add('basic')
await config.permissive.add('normal')
await config.permissive.add('expert')
# set information and owner
await config.owner.set(session_id)
await config.information.set(f'{self.dataset_name}_name', name)
except Exception as err:
if get_config()['global']['debug']:
print_exc()
msg = _(f'unable to load config for {self.dataset_name} "{name}": {err}')
await log.error_msg(risotto_context,
None,
msg,
)
return config
async def _deploy_configuration(self,
dico: dict,
) -> None:
config_std = dico['config_to_deploy']
config = dico['config']
# when deploy, calculate force_store_value
ro = await config_std.property.getdefault('read_only', 'append')
if 'force_store_value' not in ro:
await config_std.property.read_write()
if self.dataset_name == 'servermodel':
# server_deployed should be hidden
await config_std.forcepermissive.option('configuration.general.server_deployed').value.set(True)
ro = frozenset(list(ro) + ['force_store_value'])
rw = await config_std.property.getdefault('read_write', 'append')
rw = frozenset(list(rw) + ['force_store_value'])
await config_std.property.setdefault(ro, 'read_only', 'append')
await config_std.property.setdefault(rw, 'read_write', 'append')
await config_std.property.read_only()
# copy informations from 'to deploy' configuration to configuration
await config.information.importation(await config_std.information.exportation())
await config.value.importation(await config_std.value.exportation())
await config.permissive.importation(await config_std.permissive.exportation())
await config.property.importation(await config_std.property.exportation())
async def build_configuration(self,
config: Config,
) -> dict:
configuration = {}
for option in await config.option.list('optiondescription'):
name = await option.option.name()
if name == 'services':
continue
if name == RougailConfig['variable_namespace']:
fullpath = False
flatten = True
else:
fullpath = True
flatten = False
configuration.update(await option.value.dict(leader_to_list=True, fullpath=fullpath, flatten=flatten))
return configuration

View File

@ -18,9 +18,6 @@ from .context import Context
from . import register from . import register
DISPATCHER = None
class CallDispatcher: class CallDispatcher:
async def valid_call_returns(self, async def valid_call_returns(self,
risotto_context: Context, risotto_context: Context,
@ -33,11 +30,14 @@ class CallDispatcher:
if response.impl_get_information('multi'): if response.impl_get_information('multi'):
if not isinstance(returns, list): if not isinstance(returns, list):
err = _(f'function {module_name}.{function_name} has to return a list') err = _(f'function {module_name}.{function_name} has to return a list')
raise CallError(err) await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
else: else:
if not isinstance(returns, dict): if not isinstance(returns, dict):
await log.error_msg(risotto_context, kwargs, returns)
err = _(f'function {module_name}.{function_name} has to return a dict') err = _(f'function {module_name}.{function_name} has to return a dict')
raise CallError(err) await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
returns = [returns] returns = [returns]
if response is None: if response is None:
raise Exception('hu?') raise Exception('hu?')
@ -45,22 +45,17 @@ class CallDispatcher:
for ret in returns: for ret in returns:
async with await Config(response, display_name=lambda self, dyn_name, suffix: self.impl_getname()) as config: async with await Config(response, display_name=lambda self, dyn_name, suffix: self.impl_getname()) as config:
await config.property.read_write() await config.property.read_write()
key = None
try: try:
for key, value in ret.items(): for key, value in ret.items():
await config.option(key).value.set(value) await config.option(key).value.set(value)
except AttributeError as err: except AttributeError:
if key is not None: err = _(f'function {module_name}.{function_name} return the unknown parameter "{key}" for the uri "{risotto_context.version}.{risotto_context.message}"')
err = _(f'function {module_name}.{function_name} return the unknown parameter "{key}" for the uri "{risotto_context.version}.{risotto_context.message}"') await log.error_msg(risotto_context, kwargs, err)
else: raise CallError(str(err))
err = _(f'function {module_name}.{function_name} return unconsistency data "{err}" for the uri "{risotto_context.version}.{risotto_context.message}"') except ValueError:
raise CallError(err) err = _(f'function {module_name}.{function_name} return the parameter "{key}" with an unvalid value "{value}" for the uri "{risotto_context.version}.{risotto_context.message}"')
except ValueError as err: await log.error_msg(risotto_context, kwargs, err)
if key is not None: raise CallError(str(err))
err = _(f'function {module_name}.{function_name} return the invalid parameter "{key}" for the uri "{risotto_context.version}.{risotto_context.message}": {err}')
else:
err = _(f'function {module_name}.{function_name} return unconsistency error for the uri "{risotto_context.version}.{risotto_context.message}": {err}')
raise CallError(err)
await config.property.read_only() await config.property.read_only()
mandatories = await config.value.mandatory() mandatories = await config.value.mandatory()
if mandatories: if mandatories:
@ -70,7 +65,8 @@ class CallDispatcher:
await config.value.dict() await config.value.dict()
except Exception as err: except Exception as err:
err = _(f'function {module_name}.{function_name} return an invalid response {err} for the uri "{risotto_context.version}.{risotto_context.message}"') err = _(f'function {module_name}.{function_name} return an invalid response {err} for the uri "{risotto_context.version}.{risotto_context.message}"')
raise CallError(err) await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
async def call(self, async def call(self,
version: str, version: str,
@ -93,16 +89,9 @@ class CallDispatcher:
if message not in self.messages[version]: if message not in self.messages[version]:
raise CallError(_(f'cannot find message "{version}.{message}"')) raise CallError(_(f'cannot find message "{version}.{message}"'))
function_obj = self.messages[version][message] function_obj = self.messages[version][message]
# log # do not start a new database connection
function_name = function_obj['function'].__name__
info_msg = _(f"call function {function_obj['full_module_name']}.{function_name}")
if hasattr(old_risotto_context, 'connection'): if hasattr(old_risotto_context, 'connection'):
# do not start a new database connection
risotto_context.connection = old_risotto_context.connection risotto_context.connection = old_risotto_context.connection
await log.start(risotto_context,
kwargs,
info_msg,
)
await self.check_message_type(risotto_context, await self.check_message_type(risotto_context,
kwargs, kwargs,
) )
@ -112,23 +101,22 @@ class CallDispatcher:
check_role, check_role,
internal, internal,
) )
try: return await self.launch(risotto_context,
ret = await self.launch(risotto_context, kwargs,
kwargs, config_arguments,
config_arguments, function_obj,
function_obj, )
)
await log.success(risotto_context,
ret,
)
except Exception as err:
await log.failed(risotto_context,
str(err),
)
raise CallError(err) from err
else: else:
error = None
try: try:
await self.check_message_type(risotto_context,
kwargs,
)
config_arguments = await self.load_kwargs_to_config(risotto_context,
f'{version}.{message}',
kwargs,
check_role,
internal,
)
async with self.pool.acquire() as connection: async with self.pool.acquire() as connection:
await connection.set_type_codec( await connection.set_type_codec(
'json', 'json',
@ -138,56 +126,28 @@ class CallDispatcher:
) )
risotto_context.connection = connection risotto_context.connection = connection
async with connection.transaction(): async with connection.transaction():
try: return await self.launch(risotto_context,
await log.start(risotto_context, kwargs,
kwargs, config_arguments,
info_msg, function_obj,
) )
await self.check_message_type(risotto_context,
kwargs,
)
config_arguments = await self.load_kwargs_to_config(risotto_context,
f'{version}.{message}',
kwargs,
check_role,
internal,
)
ret = await self.launch(risotto_context,
kwargs,
config_arguments,
function_obj,
)
# log the success
await log.success(risotto_context,
ret,
)
if not internal and isinstance(ret, dict):
ret['context_id'] = risotto_context.context_id
except CallError as err:
if get_config()['global']['debug']:
print_exc()
await log.failed(risotto_context,
str(err),
)
raise err from err
except CallError as err: except CallError as err:
error = err raise err
except Exception as err: except Exception as err:
# if there is a problem with arguments, just send an error and do nothing # if there is a problem with arguments, just send an error and do nothing
if get_config()['global']['debug']: if get_config()['global']['debug']:
print_exc() print_exc()
await log.failed(risotto_context, async with self.pool.acquire() as connection:
str(err), await connection.set_type_codec(
) 'json',
error = err encoder=dumps,
if error: decoder=loads,
if not internal: schema='pg_catalog'
err = CallError(str(error)) )
err.context_id = risotto_context.context_id risotto_context.connection = connection
else: async with connection.transaction():
err = error await log.error_msg(risotto_context, kwargs, err)
raise err from error raise err
return ret
class PublishDispatcher: class PublishDispatcher:
@ -199,6 +159,8 @@ class PublishDispatcher:
for message, message_infos in messages.items(): for message, message_infos in messages.items():
# event not emit locally # event not emit locally
if message_infos['pattern'] == 'event' and 'functions' in message_infos and message_infos['functions']: if message_infos['pattern'] == 'event' and 'functions' in message_infos and message_infos['functions']:
# module, submodule, submessage = message.split('.', 2)
# if f'{module}.{submodule}' not in self.injected_self:
uri = f'{version}.{message}' uri = f'{version}.{message}'
print(f' - {uri}') print(f' - {uri}')
await self.listened_connection.add_listener(uri, await self.listened_connection.add_listener(uri,
@ -218,7 +180,6 @@ class PublishDispatcher:
remote_kw = dumps({'kwargs': kwargs, remote_kw = dumps({'kwargs': kwargs,
'context': {'username': risotto_context.username, 'context': {'username': risotto_context.username,
'paths': risotto_context.paths, 'paths': risotto_context.paths,
'context_id': risotto_context.context_id,
} }
}) })
# FIXME should be better :/ # FIXME should be better :/
@ -234,34 +195,21 @@ class PublishDispatcher:
version, message = uri.split('.', 1) version, message = uri.split('.', 1)
loop = get_event_loop() loop = get_event_loop()
remote_kw = loads(payload) remote_kw = loads(payload)
for function_obj in self.messages[version][message]['functions']: risotto_context = self.build_new_context(remote_kw['context'],
risotto_context = self.build_new_context(remote_kw['context'], version,
version, message,
message, 'event',
'event', )
) callback = lambda: ensure_future(self._publish(version,
callback = self.get_callback(version, message, function_obj, risotto_context, remote_kw['kwargs'],) message,
loop.call_soon(callback) risotto_context,
**remote_kw['kwargs'],
def get_callback(self, ))
version, loop.call_soon(callback)
message,
function_obj,
risotto_context,
kwargs,
):
return lambda: ensure_future(self._publish(version,
message,
function_obj,
risotto_context,
**kwargs,
))
async def _publish(self, async def _publish(self,
version: str, version: str,
message: str, message: str,
function_obj,
risotto_context: Context, risotto_context: Context,
**kwargs, **kwargs,
) -> None: ) -> None:
@ -271,54 +219,46 @@ class PublishDispatcher:
False, False,
False, False,
) )
async with self.pool.acquire() as connection: for function_obj in self.messages[version][message]['functions']:
await connection.set_type_codec( async with self.pool.acquire() as connection:
'json', try:
encoder=dumps, await self.check_message_type(risotto_context,
decoder=loads, kwargs,
schema='pg_catalog' )
) await connection.set_type_codec(
risotto_context.connection = connection 'json',
function_name = function_obj['function'].__name__ encoder=dumps,
info_msg = _(f"call function {function_obj['full_module_name']}.{function_name}") decoder=loads,
try: schema='pg_catalog'
async with connection.transaction(): )
try: risotto_context.connection = connection
await log.start(risotto_context, async with connection.transaction():
kwargs,
info_msg,
)
await self.check_message_type(risotto_context,
kwargs,
)
await self.launch(risotto_context, await self.launch(risotto_context,
kwargs, kwargs,
config_arguments, config_arguments,
function_obj, function_obj,
) )
# log the success except CallError as err:
await log.success(risotto_context) pass
except CallError as err: except Exception as err:
if get_config()['global']['debug']: # if there is a problem with arguments, log and do nothing
print_exc() if get_config()['global']['debug']:
await log.failed(risotto_context, print_exc()
str(err), async with self.pool.acquire() as connection:
) await connection.set_type_codec(
except CallError: 'json',
pass encoder=dumps,
except Exception as err: decoder=loads,
# if there is a problem with arguments, log and do nothing schema='pg_catalog'
if get_config()['global']['debug']: )
print_exc() risotto_context.connection = connection
await log.failed(risotto_context, async with connection.transaction():
str(err), await log.error_msg(risotto_context, kwargs, err)
)
class Dispatcher(register.RegisterDispatcher, class Dispatcher(register.RegisterDispatcher,
CallDispatcher, CallDispatcher,
PublishDispatcher, PublishDispatcher):
):
""" Manage message (call or publish) """ Manage message (call or publish)
so launch a function when a message is called so launch a function when a message is called
""" """
@ -334,13 +274,11 @@ class Dispatcher(register.RegisterDispatcher,
risotto_context = Context() risotto_context = Context()
risotto_context.username = context['username'] risotto_context.username = context['username']
risotto_context.paths = copy(context['paths']) risotto_context.paths = copy(context['paths'])
risotto_context.context_id = context['context_id']
risotto_context.paths.append(uri) risotto_context.paths.append(uri)
risotto_context.uri = uri risotto_context.uri = uri
risotto_context.type = type risotto_context.type = type
risotto_context.message = message risotto_context.message = message
risotto_context.version = version risotto_context.version = version
risotto_context.pool = self.pool
return risotto_context return risotto_context
async def check_message_type(self, async def check_message_type(self,
@ -349,6 +287,7 @@ class Dispatcher(register.RegisterDispatcher,
) -> None: ) -> None:
if self.messages[risotto_context.version][risotto_context.message]['pattern'] != risotto_context.type: if self.messages[risotto_context.version][risotto_context.message]['pattern'] != risotto_context.type:
msg = _(f'{risotto_context.uri} is not a {risotto_context.type} message') msg = _(f'{risotto_context.uri} is not a {risotto_context.type} message')
await log.error_msg(risotto_context, kwargs, msg)
raise CallError(msg) raise CallError(msg)
async def load_kwargs_to_config(self, async def load_kwargs_to_config(self,
@ -394,7 +333,7 @@ class Dispatcher(register.RegisterDispatcher,
parameters = await subconfig.value.dict() parameters = await subconfig.value.dict()
if extra_parameters: if extra_parameters:
parameters.update(extra_parameters) parameters.update(extra_parameters)
return parameters return parameters
def get_service(self, def get_service(self,
name: str): name: str):
@ -403,15 +342,14 @@ class Dispatcher(register.RegisterDispatcher,
async def check_role(self, async def check_role(self,
config: Config, config: Config,
user_login: str, user_login: str,
uri: str, uri: str) -> None:
) -> None:
async with self.pool.acquire() as connection: async with self.pool.acquire() as connection:
async with connection.transaction(): async with connection.transaction():
# Verify if user exists and get ID # Verify if user exists and get ID
sql = ''' sql = '''
SELECT UserId SELECT UserId
FROM UserUser FROM UserUser
WHERE Login = $1 WHERE UserLogin = $1
''' '''
user_id = await connection.fetchval(sql, user_id = await connection.fetchval(sql,
user_login) user_login)
@ -457,6 +395,8 @@ class Dispatcher(register.RegisterDispatcher,
# so send the message # so send the message
function = function_obj['function'] function = function_obj['function']
risotto_context.module = function_obj['module'].split('.', 1)[0] risotto_context.module = function_obj['module'].split('.', 1)[0]
function_name = function.__name__
info_msg = _(f"in function {function_obj['full_module_name']}.{function_name}")
# build argument for this function # build argument for this function
if risotto_context.type == 'rpc': if risotto_context.type == 'rpc':
kw = config_arguments kw = config_arguments
@ -467,8 +407,7 @@ class Dispatcher(register.RegisterDispatcher,
kw[key] = value kw[key] = value
kw['risotto_context'] = risotto_context kw['risotto_context'] = risotto_context
# launch returns = await function(self.injected_self[function_obj['module']], **kw)
returns = await function(self.get_service(function_obj['module']), **kw)
if risotto_context.type == 'rpc': if risotto_context.type == 'rpc':
# valid returns # valid returns
await self.valid_call_returns(risotto_context, await self.valid_call_returns(risotto_context,
@ -476,10 +415,14 @@ class Dispatcher(register.RegisterDispatcher,
returns, returns,
kwargs, kwargs,
) )
# log the success
await log.info_msg(risotto_context,
{'arguments': kwargs,
'returns': returns},
info_msg,
)
# notification # notification
if function_obj.get('notification'): if function_obj.get('notification'):
if returns is None:
raise Exception(_(f'function "{function_obj["full_module_name"]}.{function_obj["function"].__name__}" must returns something for {function_obj["notification"]}!'))
notif_version, notif_message = function_obj['notification'].split('.', 1) notif_version, notif_message = function_obj['notification'].split('.', 1)
if not isinstance(returns, list): if not isinstance(returns, list):
send_returns = [returns] send_returns = [returns]
@ -495,9 +438,5 @@ class Dispatcher(register.RegisterDispatcher,
return returns return returns
def get_dispatcher(): dispatcher = Dispatcher()
global DISPATCHER register.dispatcher = dispatcher
if DISPATCHER is None:
DISPATCHER = Dispatcher()
register.dispatcher = DISPATCHER
return DISPATCHER

View File

@ -1,4 +1,4 @@
from aiohttp.web import Application, Response, get, post, HTTPBadRequest, HTTPInternalServerError, HTTPNotFound, static from aiohttp.web import Application, Response, get, post, HTTPBadRequest, HTTPInternalServerError, HTTPNotFound
from json import dumps from json import dumps
from traceback import print_exc from traceback import print_exc
try: try:
@ -7,58 +7,41 @@ except:
from tiramisu import Config, default_storage from tiramisu import Config, default_storage
from .dispatcher import get_dispatcher from .dispatcher import dispatcher
from .utils import _ from .utils import _
from .context import Context from .context import Context
from .error import CallError, NotAllowedError, RegistrationError from .error import CallError, NotAllowedError, RegistrationError
from .message import get_messages from .message import get_messages
#from .logger import log from .logger import log
from .config import get_config from .config import get_config
from . import services from . import services
extra_routes = {} extra_routes = {}
extra_statics = {}
def create_context(request): def create_context(request):
risotto_context = Context() risotto_context = Context()
if 'username' in dict(request.match_info): risotto_context.username = request.match_info.get('username',
username = request.match_info['username'] get_config()['http_server']['default_user'],
elif 'username' in request.headers: )
username = request.headers['username']
else:
username = get_config()['http_server']['default_user']
risotto_context.username = username
return risotto_context return risotto_context
def register(version: str, def register(version: str,
path: str, path: str):
):
""" Decorator to register function to the http route """ Decorator to register function to the http route
""" """
def decorator(function): def decorator(function):
if path in extra_routes: if path in extra_routes:
raise RegistrationError(f'the route "{path}" is already registered') raise RegistrationError(f'the route {path} is already registered')
extra_routes[path] = {'function': function, extra_routes[path] = {'function': function,
'version': version, 'version': version}
}
return decorator return decorator
def register_static(path: str,
directory: str,
) -> None:
if path in extra_statics:
raise RegistrationError(f'the static path "{path}" is already registered')
extra_statics[path] = directory
class extra_route_handler: class extra_route_handler:
async def __new__(cls, async def __new__(cls, request):
request,
):
kwargs = dict(request.match_info) kwargs = dict(request.match_info)
kwargs['request'] = request kwargs['request'] = request
kwargs['risotto_context'] = create_context(request) kwargs['risotto_context'] = create_context(request)
@ -70,7 +53,6 @@ class extra_route_handler:
if function_name != 'risotto.http': if function_name != 'risotto.http':
risotto_module_name, submodule_name = function_name.split('.', 2)[:-1] risotto_module_name, submodule_name = function_name.split('.', 2)[:-1]
module_name = risotto_module_name.split('_')[-1] module_name = risotto_module_name.split('_')[-1]
dispatcher = get_dispatcher()
kwargs['self'] = dispatcher.injected_self[module_name + '.' + submodule_name] kwargs['self'] = dispatcher.injected_self[module_name + '.' + submodule_name]
try: try:
returns = await cls.function(**kwargs) returns = await cls.function(**kwargs)
@ -85,8 +67,7 @@ class extra_route_handler:
# await log.info_msg(kwargs['risotto_context'], # await log.info_msg(kwargs['risotto_context'],
# dict(request.match_info)) # dict(request.match_info))
return Response(text=dumps(returns), return Response(text=dumps(returns),
content_type='application/json', content_type='application/json')
)
async def handle(request): async def handle(request):
@ -94,7 +75,6 @@ async def handle(request):
risotto_context = create_context(request) risotto_context = create_context(request)
kwargs = await request.json() kwargs = await request.json()
try: try:
dispatcher = get_dispatcher()
pattern = dispatcher.messages[version][message]['pattern'] pattern = dispatcher.messages[version][message]['pattern']
if pattern == 'rpc': if pattern == 'rpc':
method = dispatcher.call method = dispatcher.call
@ -107,44 +87,25 @@ async def handle(request):
internal=False, internal=False,
**kwargs, **kwargs,
) )
except NotAllowedError as err:
raise HTTPNotFound(reason=str(err))
except CallError as err:
raise HTTPBadRequest(reason=str(err).replace('\n', ' '))
except Exception as err: except Exception as err:
context_id = None if get_config()['global']['debug']:
if isinstance(err, NotAllowedError): print_exc()
error_type = HTTPNotFound raise HTTPInternalServerError(reason=str(err))
elif isinstance(err, CallError): return Response(text=dumps({'response': text}),
error_type = HTTPBadRequest content_type='application/json')
context_id = err.context_id
else:
if get_config()['global']['debug']:
print_exc()
error_type = HTTPInternalServerError
response = {'type': 'error',
'reason': str(err).replace('\n', ' '),
}
if context_id is not None:
response['context_id'] = context_id
err = dumps({'response': response,
'type': 'error',
})
raise error_type(text=err,
content_type='application/json',
)
return Response(text=dumps({'response': text,
'type': 'success',
}),
content_type='application/json',
)
async def api(request, async def api(request,
risotto_context, risotto_context):
):
global TIRAMISU global TIRAMISU
if not TIRAMISU: if not TIRAMISU:
# check all URI that have an associated role # check all URI that have an associated role
# all URI without role is concidered has a private URI # all URI without role is concidered has a private URI
uris = [] uris = []
dispatcher = get_dispatcher()
async with dispatcher.pool.acquire() as connection: async with dispatcher.pool.acquire() as connection:
async with connection.transaction(): async with connection.transaction():
# Check role with ACL # Check role with ACL
@ -169,8 +130,7 @@ async def api(request,
async def get_app(loop): async def get_app(loop):
""" build all routes """ build all routes
""" """
global extra_routes, extra_statics global extra_routes
dispatcher = get_dispatcher()
services.link_to_dispatcher(dispatcher) services.link_to_dispatcher(dispatcher)
app = Application(loop=loop) app = Application(loop=loop)
routes = [] routes = []
@ -192,8 +152,7 @@ async def get_app(loop):
for version in versions: for version in versions:
api_route = {'function': api, api_route = {'function': api,
'version': version, 'version': version,
'path': f'/api/{version}', 'path': f'/api/{version}'}
}
extra_handler = type(api_route['path'], (extra_route_handler,), api_route) extra_handler = type(api_route['path'], (extra_route_handler,), api_route)
routes.append(get(api_route['path'], extra_handler)) routes.append(get(api_route['path'], extra_handler))
print(f' - {api_route["path"]} (http_get)') print(f' - {api_route["path"]} (http_get)')
@ -210,22 +169,12 @@ async def get_app(loop):
extra_handler = type(path, (extra_route_handler,), extra) extra_handler = type(path, (extra_route_handler,), extra)
routes.append(get(path, extra_handler)) routes.append(get(path, extra_handler))
print(f' - {path} (http_get)') print(f' - {path} (http_get)')
if extra_statics:
if not extra_routes:
print(_('======== Registered static routes ========'))
for path, directory in extra_statics.items():
routes.append(static(path, directory))
print(f' - {path} (static)')
del extra_routes del extra_routes
del extra_statics
app.router.add_routes(routes) app.router.add_routes(routes)
await dispatcher.register_remote() await dispatcher.register_remote()
print() print()
await dispatcher.on_join() await dispatcher.on_join()
return await loop.create_server(app.make_handler(), return await loop.create_server(app.make_handler(), '*', get_config()['http_server']['port'])
'*',
get_config()['http_server']['port'],
)
TIRAMISU = None TIRAMISU = None

View File

@ -1,378 +0,0 @@
from os import listdir, walk, makedirs
from os.path import isfile, isdir, join, dirname
from yaml import load, SafeLoader
from json import load as jload, dump as jdump
from time import time
from shutil import copy2, rmtree, move
from hashlib import sha512
from subprocess import Popen
from rougail import RougailConvert, RougailConfig, RougailUpgrade
try:
from tiramisu3 import Config
except:
from tiramisu import Config
from .utils import _
DATASET_PATH = '/usr/share/risotto/'
TMP_DIRECTORY = '/tmp'
PACKER_TMP_DIRECTORY = join(TMP_DIRECTORY, 'packer')
PACKER_FILE_NAME = 'recipe.json'
IMAGES_DIRECTORY = join(TMP_DIRECTORY, 'images')
FUNCTIONS = b"""try:
from tiramisu3 import valid_network_netmask, valid_ip_netmask, valid_broadcast, valid_in_network, valid_not_equal as valid_differ, valid_not_equal, calc_value
except:
from tiramisu import valid_network_netmask, valid_ip_netmask, valid_broadcast, valid_in_network, valid_not_equal as valid_differ, valid_not_equal, calc_value
# =============================================================
# fork of risotto-setting/src/risotto_setting/config/config.py
def get_password(**kwargs):
return 'password'
def get_ip(**kwargs):
return '1.1.1.1'
def get_chain(**kwargs):
return 'chain'
def get_certificates(**kwargs):
return []
def get_certificate(**kwargs):
return 'certificate'
def get_private_key(**kwargs):
return 'private_key'
def get_linked_configuration(**kwargs):
if 'test' in kwargs and kwargs['test']:
return kwargs['test'][0]
return 'configuration'
def zone_information(**kwargs):
return 'zone'
# =============================================================
"""
class Images:
def __init__(self,
image_dir: str=None,
tmp_dir: str=None,
):
if image_dir is None:
image_dir = IMAGES_DIRECTORY
self.image_dir = image_dir
if isdir(self.image_dir):
rmtree(self.image_dir)
if tmp_dir is None:
tmp_dir = PACKER_TMP_DIRECTORY
self.tmp_dir = tmp_dir
self.load_applications()
def load_applications(self) -> None:
self.build_images = []
self.applications = {}
for distrib in listdir(join(DATASET_PATH, 'seed')):
distrib_dir = join(DATASET_PATH, 'seed', distrib, 'applicationservice')
if not isdir(distrib_dir):
continue
for release in listdir(distrib_dir):
release_dir = join(distrib_dir, release)
if not isdir(release_dir):
continue
for applicationservice in listdir(release_dir):
applicationservice_dir = join(release_dir, applicationservice)
if not isdir(applicationservice_dir):
continue
if applicationservice in self.applications:
raise Exception('multi applicationservice')
with open(join(applicationservice_dir, 'applicationservice.yml')) as yaml:
app = load(yaml, Loader=SafeLoader)
self.applications[applicationservice] = {'path': applicationservice_dir,
'yml': app,
}
if 'service' in app and app['service']:
self.build_images.append(applicationservice)
def calc_depends(self,
dependencies: list,
appname,
key_is_name=False,
):
app = self.applications[appname]['yml']
if not 'depends' in app or not app['depends']:
return
for dependency in app['depends']:
if key_is_name:
key = appname
else:
key = self.applications[dependency]['path']
if key not in dependencies:
dependencies.insert(0, key)
self.calc_depends(dependencies, dependency, key_is_name)
def list_oses(self):
oses = set()
for build in self.build_images:
dependencies = [build]
self.calc_depends(dependencies, build, True)
for dependency in dependencies:
if isdir(join(self.applications[dependency]['path'], 'packer', 'os')):
oses.add(dependency)
break
for os in oses:
dependencies = [self.applications[os]['path']]
self.calc_depends(dependencies, os)
yield os, dependencies
def list_images(self):
for build in self.build_images:
dependencies = [self.applications[build]['path']]
self.calc_depends(dependencies, build)
yield build, dependencies
async def build(self) -> None:
if isdir(self.tmp_dir):
rmtree(self.tmp_dir)
image = Image(self.image_dir,
self.tmp_dir,
)
print(_('Build OSes'))
if not isdir(join(self.image_dir, 'os')):
makedirs(join(self.image_dir, 'os'))
for application, dependencies_path in self.list_oses():
print(_(f'Build OS {application}'))
await image.build_os(application,
dependencies_path,
)
print(_('Build images'))
for application, dependencies_path in self.list_images():
print(_(f'Build image {application}'))
await image.build_image(application,
dependencies_path,
)
class Image:
def __init__(self,
image_dir: str,
tmp_dir: str,
):
self.image_dir = image_dir
self.tmp_dir = tmp_dir
@staticmethod
def copy_files(dependencies_path: list,
dst_path: str,
element: str,
) -> None:
for dependency_path in dependencies_path:
src_path = join(dependency_path,
'packer',
element,
)
root_len = len(src_path) + 1
for dir_name, subdir_names, filenames in walk(src_path):
subdir = join(dst_path, dir_name[root_len:])
if not isdir(subdir):
makedirs(subdir)
for filename in filenames:
path = join(dir_name, filename)
sub_dst_path = join(subdir, filename)
if isfile(sub_dst_path):
raise Exception(_(f'Try to copy {sub_dst_path} which is already exists'))
copy2(path, sub_dst_path)
async def load_configuration(self,
dependencies_path: list,
packer_tmp_directory: str,
) -> dict:
config = RougailConfig.copy()
dictionaries = [join(dependency_path, 'dictionaries') for dependency_path in dependencies_path if isdir(join(dependency_path, 'dictionaries'))]
upgrade = RougailUpgrade()
dest_dictionaries = join(packer_tmp_directory, 'dictionaries')
makedirs(dest_dictionaries)
dest_dictionaries_extras = join(packer_tmp_directory, 'dictionaries_extras')
makedirs(dest_dictionaries_extras)
for dependency_path in dependencies_path:
dictionaries_dir = join(dependency_path, 'dictionaries')
if isdir(dictionaries_dir):
upgrade.load_xml_from_folders(dictionaries_dir,
dest_dictionaries,
RougailConfig['variable_namespace'],
)
extra_dir = join(dependency_path, 'extras', 'packer')
if isdir(extra_dir):
upgrade.load_xml_from_folders(extra_dir,
dest_dictionaries_extras,
'packer',
)
config['dictionaries_dir'] = [dest_dictionaries]
config['extra_dictionaries'] = {'packer': [dest_dictionaries_extras]}
self.merge_funcs(config, dependencies_path, packer_tmp_directory)
packer_configuration = await self.get_packer_information(config, packer_tmp_directory)
return packer_configuration
@staticmethod
def merge_funcs(config: RougailConfig,
dependencies_path: list,
packer_tmp_directory: str,
):
functions = FUNCTIONS
for dependency_path in dependencies_path:
funcs_dir = join(dependency_path, 'funcs')
if not isdir(funcs_dir):
continue
for func in listdir(funcs_dir):
with open(join(funcs_dir, func), 'rb') as fh:
functions += fh.read()
func_name = join(packer_tmp_directory, 'func.py')
with open(func_name, 'wb') as fh:
fh.write(functions)
config['functions_file'] = func_name
@staticmethod
async def get_packer_information(config: RougailConfig,
packer_tmp_directory: str,
) -> dict:
eolobj = RougailConvert(config)
xml = eolobj.save(join(packer_tmp_directory, 'tiramisu.py'))
optiondescription = {}
exec(xml, None, optiondescription)
config = await Config(optiondescription['option_0'])
return await config.option('packer').value.dict(leader_to_list=True, flatten=True)
@staticmethod
def do_recipe_checksum(path: str,
) -> str:
files = []
root_len = len(path) + 1
for dir_name, subdir_names, filenames in walk(path):
subpath = dir_name[root_len:]
for filename in filenames:
with open(join(dir_name, filename), 'rb') as fh:
ctl_sum = sha512(fh.read()).hexdigest()
abs_path = join(subpath, filename)
files.append(f'{abs_path}/{ctl_sum}')
files.sort()
print(files, sha512('\n'.join(files).encode()).hexdigest())
return sha512('\n'.join(files).encode()).hexdigest()
def get_tmp_directory(self,
application: str,
) -> str:
return join(self.tmp_dir,
application + '_' + str(time()),
)
def get_os_filename(self,
packer_configuration: dict,
) -> str:
return join(self.image_dir,
'os',
packer_configuration['os_name'] + '_' + packer_configuration['os_version'] + '.img',
)
def get_image_filename(self,
recipe_checksum: str,
) -> str:
return join(self.image_dir,
f'{recipe_checksum}.img',
)
async def build_os(self,
application: str,
dependencies_path: list,
) -> None:
packer_tmp_directory = self.get_tmp_directory(application)
packer_configuration = await self.load_configuration(dependencies_path, packer_tmp_directory)
packer_dst_os_filename = self.get_os_filename(packer_configuration)
self.copy_files(dependencies_path,
packer_tmp_directory,
'os',
)
packer_configuration['tmp_directory'] = packer_tmp_directory
recipe = {'variables': packer_configuration}
self.build(packer_dst_os_filename,
packer_tmp_directory,
recipe,
)
async def build_image(self,
application: str,
dependencies_path: list,
) -> None:
packer_tmp_directory = self.get_tmp_directory(application)
makedirs(packer_tmp_directory)
self.copy_files(dependencies_path,
packer_tmp_directory,
'image',
)
recipe_checksum = self.do_recipe_checksum(packer_tmp_directory)
packer_dst_filename = self.get_image_filename(recipe_checksum)
packer_configuration = await self.load_configuration(dependencies_path, packer_tmp_directory)
packer_dst_os_filename = join(self.image_dir,
'os',
packer_configuration['os_name'] + '_' + packer_configuration['os_version'] + '.img',
)
packer_configuration['tmp_directory'] = packer_tmp_directory
recipe = {'variables': packer_configuration}
recipe['variables']['iso_url'] = packer_dst_os_filename
self.build(packer_dst_filename,
packer_tmp_directory,
recipe,
f'{packer_dst_os_filename}.sha256',
)
@staticmethod
def build(packer_dst_filename: str,
tmp_directory: str,
recipe: dict,
sha_file: str=None,
) -> None:
packer_filename = join(tmp_directory, PACKER_FILE_NAME)
if sha_file is not None:
with open(sha_file, 'r') as fh:
sha256 = fh.read().split(' ', 1)[0]
recipe['variables']['iso_checksum'] = sha256
with open(packer_filename, 'r') as recipe_fd:
for key, value in jload(recipe_fd).items():
recipe[key] = value
with open(packer_filename, 'w') as recipe_fd:
jdump(recipe, recipe_fd, indent=2)
preprocessors = join(tmp_directory, 'preprocessors')
if isfile(preprocessors):
proc = Popen([preprocessors],
#stdout=PIPE,
#stderr=PIPE,
cwd=tmp_directory,
)
proc.wait()
if proc.returncode:
raise Exception(_(f'error when executing {preprocessors}'))
proc = Popen(['packer', 'build', packer_filename],
#stdout=PIPE,
#stderr=PIPE,
cwd=tmp_directory,
)
proc.wait()
if proc.returncode:
raise Exception(_(f'cannot build {packer_dst_filename} with {packer_filename}'))
if not isdir(dirname(packer_dst_filename)):
makedirs(dirname(packer_dst_filename))
move(join(tmp_directory, 'image.img'), packer_dst_filename)
move(join(tmp_directory, 'image.sha256'), f'{packer_dst_filename}.sha256')
print(_(f'Image {packer_dst_filename} created'))
rmtree(tmp_directory)

View File

@ -1,87 +1,38 @@
from typing import Dict, Any, Optional from typing import Dict, Any
from json import dumps, loads from json import dumps
from asyncpg.exceptions import UndefinedTableError from asyncpg.exceptions import UndefinedTableError
from datetime import datetime
from asyncio import Lock
from .context import Context from .context import Context
from .utils import _ from .utils import _
from .config import get_config from .config import get_config
database_lock = Lock()
LEVELS = ['Error', 'Info', 'Success', 'Started', 'Failure']
class Logger: class Logger:
""" An object to manager log """ An object to manager log
""" """
def __init__(self) -> None:
self.log_connection = None
async def get_connection(self,
risotto_context: Context,
):
if not self.log_connection:
self.log_connection = await risotto_context.pool.acquire()
await self.log_connection.set_type_codec(
'json',
encoder=dumps,
decoder=loads,
schema='pg_catalog'
)
return self.log_connection
async def insert(self, async def insert(self,
msg: str, msg: str,
risotto_context: Context, path: str,
risotto_context: str,
level: str, level: str,
kwargs: Any=None, data: Any= None) -> None:
start: bool=False, insert = 'INSERT INTO log(Msg, Path, Username, Level'
) -> None: values = 'VALUES($1,$2,$3,$4'
uri = self._get_last_uri(risotto_context) args = [msg, path, risotto_context.username, level]
uris = " ".join(risotto_context.paths) if data:
insert = 'INSERT INTO RisottoLog(Msg, URI, URIS, UserLogin, Status' insert += ', Data'
values = 'VALUES($1,$2,$3,$4,$5' values += ',$5'
args = [msg, uri, uris, risotto_context.username, LEVELS.index(level)] args.append(dumps(data))
if kwargs:
insert += ', Kwargs'
values += ',$6'
args.append(dumps(kwargs))
context_id = risotto_context.context_id
if context_id is not None:
insert += ', ContextId'
if kwargs:
values += ',$7'
else:
values += ',$6'
args.append(context_id)
sql = insert + ') ' + values + ') RETURNING LogId' sql = insert + ') ' + values + ')'
try: try:
async with database_lock: await risotto_context.connection.fetch(sql, *args)
connection = await self.get_connection(risotto_context)
log_id = await connection.fetchval(sql, *args)
if context_id is None and start:
risotto_context.context_id = log_id
if start:
risotto_context.start_id = log_id
except UndefinedTableError as err: except UndefinedTableError as err:
raise Exception(_(f'cannot access to database ({err}), was the database really created?')) raise Exception(_(f'cannot access to database ({err}), was the database really created?'))
def _get_last_uri(self,
risotto_context: Context,
) -> str:
if risotto_context.paths:
return risotto_context.paths[-1]
return ''
def _get_message_paths(self, def _get_message_paths(self,
risotto_context: Context, risotto_context: Context):
) -> str:
if not risotto_context.paths:
return ''
paths = risotto_context.paths paths = risotto_context.paths
if risotto_context.type: if risotto_context.type:
paths_msg = f' {risotto_context.type} ' paths_msg = f' {risotto_context.type} '
@ -98,114 +49,44 @@ class Logger:
risotto_context: Context, risotto_context: Context,
arguments, arguments,
error: str, error: str,
msg: str='', msg: str=''):
):
""" send message when an error append """ send message when an error append
""" """
paths_msg = self._get_message_paths(risotto_context) paths_msg = self._get_message_paths(risotto_context)
print(_(f'{risotto_context.username}: ERROR: {error} ({paths_msg} with arguments "{arguments}": {msg})')) print(_(f'{risotto_context.username}: ERROR: {error} ({paths_msg} with arguments "{arguments}": {msg})'))
await self.insert(msg, await self.insert(msg,
paths_msg,
risotto_context, risotto_context,
'Error', 'Error',
arguments, arguments)
)
async def info_msg(self, async def info_msg(self,
risotto_context: Context, risotto_context: Context,
arguments: Dict, arguments: Dict,
msg: str='', msg: str=''):
) -> None:
""" send message with common information """ send message with common information
""" """
paths_msg = self._get_message_paths(risotto_context) if risotto_context.paths:
paths_msg = self._get_message_paths(risotto_context)
else:
paths_msg = ''
if get_config()['global']['debug']: if get_config()['global']['debug']:
print(_(f'{risotto_context.username}: INFO:{paths_msg}: {msg}')) print(_(f'{risotto_context.username}: INFO:{paths_msg}: {msg}'))
await self.insert(msg, await self.insert(msg,
paths_msg,
risotto_context, risotto_context,
'Info', 'Info',
arguments, arguments)
)
async def start(self,
risotto_context: Context,
arguments: dict,
msg: str,
) -> None:
paths_msg = self._get_message_paths(risotto_context)
if get_config()['global']['debug']:
if risotto_context.context_id != None:
context = f'({risotto_context.context_id})'
else:
context = ''
print(_(f'{risotto_context.username}: START{context}:{paths_msg}: {msg}'))
await self.insert(msg,
risotto_context,
'Started',
arguments,
start=True,
)
async def success(self,
risotto_context: Context,
returns: Optional[dict]=None,
) -> None:
if get_config()['global']['debug']:
paths_msg = self._get_message_paths(risotto_context)
print(_(f'{risotto_context.username}: SUCCESS({risotto_context.context_id}):{paths_msg}'))
sql = """UPDATE RisottoLog
SET StopDate = $2,
Status = $3
"""
args = [datetime.now(), LEVELS.index('Success')]
if returns:
sql += """, Returns = $4
"""
args.append(dumps(returns))
sql += """WHERE LogId = $1
"""
async with database_lock:
connection = await self.get_connection(risotto_context)
await connection.execute(sql,
risotto_context.start_id,
*args,
)
async def failed(self,
risotto_context: Context,
err: str,
) -> None:
if get_config()['global']['debug']:
paths_msg = self._get_message_paths(risotto_context)
if risotto_context.context_id != None:
context = f'({risotto_context.context_id})'
else:
context = ''
print(_(f'{risotto_context.username}: FAILED({risotto_context.context_id}):{paths_msg}: {err}'))
sql = """UPDATE RisottoLog
SET StopDate = $2,
Status = $4,
Msg = $3
WHERE LogId = $1
"""
async with database_lock:
connection = await self.get_connection(risotto_context)
await connection.execute(sql,
risotto_context.start_id,
datetime.now(),
err[:254],
LEVELS.index('Failure'),
)
async def info(self, async def info(self,
risotto_context, risotto_context,
msg, msg):
):
if get_config()['global']['debug']: if get_config()['global']['debug']:
print(msg) print(msg)
await self.insert(msg, await self.insert(msg,
None,
risotto_context, risotto_context,
'Info', 'Info')
)
log = Logger() log = Logger()

View File

@ -19,8 +19,8 @@ from .utils import _
MESSAGE_ROOT_PATH = get_config()['global']['message_root_path'] MESSAGE_ROOT_PATH = get_config()['global']['message_root_path']
groups.addgroup('message') groups.addgroup('message')
CUSTOMTYPES = None MESSAGE_TRANSLATION = translation('risotto-message', join(MESSAGE_ROOT_PATH, '..', 'locale')).gettext
MESSAGE_TRANSLATION = None
class DictOption(Option): class DictOption(Option):
@ -313,7 +313,6 @@ class CustomParam:
'string': 'String', 'string': 'String',
'number': 'Number', 'number': 'Number',
'object': 'Dict', 'object': 'Dict',
'any': 'Any',
'array': 'Array', 'array': 'Array',
'file': 'File', 'file': 'File',
'float': 'Float'} 'float': 'Float'}
@ -449,7 +448,6 @@ def _get_option(name,
'reverse_condition': ParamValue(True)}), 'reverse_condition': ParamValue(True)}),
calc_value_property_help)) calc_value_property_help))
props.append('notunique')
description = arg.description.strip().rstrip() description = arg.description.strip().rstrip()
kwargs = {'name': name, kwargs = {'name': name,
'doc': _get_description(description, name), 'doc': _get_description(description, name),
@ -525,7 +523,6 @@ def _parse_responses(message_def,
'Number': IntOption, 'Number': IntOption,
'Boolean': BoolOption, 'Boolean': BoolOption,
'Dict': DictOption, 'Dict': DictOption,
'Any': AnyOption,
'Float': FloatOption, 'Float': FloatOption,
# FIXME # FIXME
'File': StrOption}.get(type_) 'File': StrOption}.get(type_)
@ -533,9 +530,8 @@ def _parse_responses(message_def,
raise Exception(f'unknown param type {obj.type} in responses of message {message_def.message}') raise Exception(f'unknown param type {obj.type} in responses of message {message_def.message}')
if hasattr(obj, 'default'): if hasattr(obj, 'default'):
kwargs['default'] = obj.default kwargs['default'] = obj.default
kwargs['properties'] = ('notunique',)
else: else:
kwargs['properties'] = ('mandatory', 'notunique') kwargs['properties'] = ('mandatory',)
options.append(option(**kwargs)) options.append(option(**kwargs))
od = OptionDescription(uri, od = OptionDescription(uri,
message_def.response.description, message_def.response.description,
@ -593,11 +589,6 @@ def get_messages(current_module_names,
): ):
"""generate description from yml files """generate description from yml files
""" """
global MESSAGE_TRANSLATION, CUSTOMTYPES
if MESSAGE_TRANSLATION is None:
MESSAGE_TRANSLATION = translation('risotto-message', join(MESSAGE_ROOT_PATH, '..', 'locale')).gettext
if CUSTOMTYPES is None:
CUSTOMTYPES = load_customtypes()
optiondescriptions = {} optiondescriptions = {}
optiondescriptions_info = {} optiondescriptions_info = {}
messages = list(list_messages(uris, messages = list(list_messages(uris,
@ -609,7 +600,7 @@ def get_messages(current_module_names,
select_option = ChoiceOption('message', select_option = ChoiceOption('message',
'Nom du message.', 'Nom du message.',
tuple(messages), tuple(messages),
properties=frozenset(['mandatory', 'positional', 'notunique'])) properties=frozenset(['mandatory', 'positional']))
for uri in messages: for uri in messages:
message_def = get_message(uri, message_def = get_message(uri,
current_module_names, current_module_names,
@ -637,3 +628,6 @@ def get_messages(current_module_names,
optiondescriptions, optiondescriptions,
) )
return optiondescriptions_info, root return optiondescriptions_info, root
CUSTOMTYPES = load_customtypes()

View File

@ -7,7 +7,6 @@ from typing import Callable, Optional, List
from asyncpg import create_pool from asyncpg import create_pool
from json import dumps, loads from json import dumps, loads
from pkg_resources import iter_entry_points from pkg_resources import iter_entry_points
from traceback import print_exc
import risotto import risotto
from .utils import _ from .utils import _
from .error import RegistrationError from .error import RegistrationError
@ -24,7 +23,7 @@ class Services():
def load_services(self): def load_services(self):
for entry_point in iter_entry_points(group='risotto_services'): for entry_point in iter_entry_points(group='risotto_services'):
self.services.setdefault(entry_point.name, {}) self.services.setdefault(entry_point.name, [])
self.services_loaded = True self.services_loaded = True
def load_modules(self, def load_modules(self,
@ -33,20 +32,21 @@ class Services():
for entry_point in iter_entry_points(group='risotto_modules'): for entry_point in iter_entry_points(group='risotto_modules'):
service_name, module_name = entry_point.name.split('.') service_name, module_name = entry_point.name.split('.')
if limit_services is None or service_name in limit_services: if limit_services is None or service_name in limit_services:
self.services[service_name][module_name] = entry_point.load() setattr(self, module_name, entry_point.load())
self.services[service_name].append(module_name)
self.modules_loaded = True self.modules_loaded = True
#
# def get_services(self): def get_services(self):
# if not self.services_loaded: if not self.services_loaded:
# self.load_services() self.load_services()
# return [(service, getattr(self, service)) for service in self.services] return [(s, getattr(self, s)) for s in self.services]
def get_modules(self, def get_modules(self,
limit_services: Optional[List[str]]=None, limit_services: Optional[List[str]]=None,
) -> List[str]: ) -> List[str]:
if not self.modules_loaded: if not self.modules_loaded:
self.load_modules(limit_services=limit_services) self.load_modules(limit_services=limit_services)
return [(module + '.' + submodule, entry_point) for module, submodules in self.services.items() for submodule, entry_point in submodules.items()] return [(module + '.' + submodule, getattr(self, submodule)) for module, submodules in self.services.items() for submodule in submodules]
def get_services_list(self): def get_services_list(self):
return self.services.keys() return self.services.keys()
@ -83,16 +83,12 @@ def register(uris: str,
uris = [uris] uris = [uris]
def decorator(function): def decorator(function):
try: for uri in uris:
for uri in uris: version, message = uri.split('.', 1)
dispatcher.set_function(uri, dispatcher.set_function(version,
notification, message,
function, notification,
function.__module__ function)
)
except NameError:
# if you when register uri, please use get_dispatcher before registered uri
pass
return decorator return decorator
@ -111,7 +107,6 @@ class RegisterDispatcher:
version = obj['version'] version = obj['version']
if version not in self.messages: if version not in self.messages:
self.messages[version] = {} self.messages[version] = {}
obj['message'] = tiramisu_message
self.messages[version][tiramisu_message] = obj self.messages[version][tiramisu_message] = obj
def get_function_args(self, def get_function_args(self,
@ -190,20 +185,21 @@ class RegisterDispatcher:
raise RegistrationError(_(f'error with {module_name}.{function_name} arguments: {msg}')) raise RegistrationError(_(f'error with {module_name}.{function_name} arguments: {msg}'))
def set_function(self, def set_function(self,
uri: str, version: str,
message: str,
notification: str, notification: str,
function: Callable, function: Callable,
full_module_name: str,
): ):
""" register a function to an URI """ register a function to an URI
URI is a message URI is a message
""" """
version, message = uri.split('.', 1)
# check if message exists # check if message exists
if message not in self.messages[version]: if message not in self.messages[version]:
raise RegistrationError(_(f'the message {message} not exists')) raise RegistrationError(_(f'the message {message} not exists'))
# xxx submodule can only be register with v1.yyy.xxx..... message # xxx submodule can only be register with v1.yyy.xxx..... message
full_module_name = function.__module__
risotto_module_name, submodule_name = full_module_name.split('.')[-3:-1] risotto_module_name, submodule_name = full_module_name.split('.')[-3:-1]
module_name = risotto_module_name.split('_')[-1] module_name = risotto_module_name.split('_')[-1]
message_module, message_submodule, message_name = message.split('.', 2) message_module, message_submodule, message_name = message.split('.', 2)
@ -219,7 +215,7 @@ class RegisterDispatcher:
# check if already register # check if already register
if 'function' in self.messages[version][message]: if 'function' in self.messages[version][message]:
raise RegistrationError(_(f'uri {uri} already registered')) raise RegistrationError(_(f'uri {version}.{message} already registered'))
# register # register
if self.messages[version][message]['pattern'] == 'rpc': if self.messages[version][message]['pattern'] == 'rpc':
@ -282,7 +278,7 @@ class RegisterDispatcher:
try: try:
self.injected_self[submodule_name] = module.Risotto(test) self.injected_self[submodule_name] = module.Risotto(test)
except AttributeError as err: except AttributeError as err:
print(_(f'unable to register the module {submodule_name}, this module must have Risotto class')) raise RegistrationError(_(f'unable to register the module {submodule_name}, this module must have Risotto class'))
def validate(self): def validate(self):
""" check if all messages have a function """ check if all messages have a function
@ -292,9 +288,9 @@ class RegisterDispatcher:
for message, message_obj in messages.items(): for message, message_obj in messages.items():
if not 'functions' in message_obj and not 'function' in message_obj: if not 'functions' in message_obj and not 'function' in message_obj:
if message_obj['pattern'] == 'event': if message_obj['pattern'] == 'event':
print(f'{version}.{message} prêche dans le désert') print(f'{message} prêche dans le désert')
else: else:
missing_messages.append(f'{version}.{message}') missing_messages.append(message)
if missing_messages: if missing_messages:
raise RegistrationError(_(f'no matching function for uri {missing_messages}')) raise RegistrationError(_(f'no matching function for uri {missing_messages}'))
@ -318,20 +314,13 @@ class RegisterDispatcher:
risotto_context.username = internal_user risotto_context.username = internal_user
risotto_context.paths.append(f'internal.{submodule_name}.on_join') risotto_context.paths.append(f'internal.{submodule_name}.on_join')
risotto_context.type = None risotto_context.type = None
risotto_context.pool = self.pool
risotto_context.connection = connection risotto_context.connection = connection
risotto_context.module = submodule_name.split('.', 1)[0] risotto_context.module = submodule_name.split('.', 1)[0]
info_msg = _(f'in function risotto_{submodule_name}.on_join') info_msg = _(f'in function risotto_{submodule_name}.on_join')
await log.info_msg(risotto_context, await log.info_msg(risotto_context,
None, None,
info_msg) info_msg)
try: await module.on_join(risotto_context)
await module.on_join(risotto_context)
except Exception as err:
if get_config()['global']['debug']:
print_exc()
msg = _(f'on_join returns an error in module {submodule_name}: {err}')
await log.error_msg(risotto_context, {}, msg)
async def load(self): async def load(self):
# valid function's arguments # valid function's arguments

View File

@ -1,27 +1,9 @@
class Undefined: class Undefined:
pass pass
undefined = Undefined()
def _(s): def _(s):
return s return s
def tiramisu_display_name(kls, undefined = Undefined()
dyn_name: 'Base'=None,
suffix: str=None,
) -> str:
if dyn_name is not None:
name = dyn_name
else:
name = kls.impl_getname()
doc = kls.impl_get_information('doc', None)
if doc:
doc = str(doc)
if doc.endswith('.'):
doc = doc[:-1]
if suffix:
doc += suffix
if name != doc:
name += f'" "{doc}'
return name

View File

@ -392,6 +392,7 @@ async def test_server_created_base():
release_distribution='last', release_distribution='last',
site_name='site_1', site_name='site_1',
zones_name=['zones'], zones_name=['zones'],
zones_ip=['1.1.1.1'],
) )
assert list(config_module.server) == [server_name] assert list(config_module.server) == [server_name]
assert set(config_module.server[server_name]) == {'server', 'server_to_deploy', 'funcs_file'} assert set(config_module.server[server_name]) == {'server', 'server_to_deploy', 'funcs_file'}
@ -419,6 +420,7 @@ async def test_server_created_own_sm():
release_distribution='last', release_distribution='last',
site_name='site_1', site_name='site_1',
zones_name=['zones'], zones_name=['zones'],
zones_ip=['1.1.1.1'],
) )
assert list(config_module.server) == [server_name] assert list(config_module.server) == [server_name]
assert set(config_module.server[server_name]) == {'server', 'server_to_deploy', 'funcs_file'} assert set(config_module.server[server_name]) == {'server', 'server_to_deploy', 'funcs_file'}
@ -467,6 +469,7 @@ async def test_server_configuration_get():
release_distribution='last', release_distribution='last',
site_name='site_1', site_name='site_1',
zones_name=['zones'], zones_name=['zones'],
zones_ip=['1.1.1.1'],
) )
# #
await config_module.server[server_name]['server'].property.read_write() await config_module.server[server_name]['server'].property.read_write()
@ -512,6 +515,7 @@ async def test_server_configuration_deployed():
release_distribution='last', release_distribution='last',
site_name='site_1', site_name='site_1',
zones_name=['zones'], zones_name=['zones'],
zones_ip=['1.1.1.1'],
) )
# #
await config_module.server[server_name]['server'].property.read_write() await config_module.server[server_name]['server'].property.read_write()