Compare commits

...

8 Commits

6 changed files with 337 additions and 36 deletions

View File

@ -113,12 +113,12 @@ if 'PASSWORD_ADMIN_EMAIL' in environ:
PASSWORD_ADMIN_EMAIL = environ['PASSWORD_ADMIN_EMAIL'] PASSWORD_ADMIN_EMAIL = environ['PASSWORD_ADMIN_EMAIL']
else: else:
# this parameter is mandatory # this parameter is mandatory
PASSWORD_ADMIN_EMAIL = config['PASSWORD_ADMIN_EMAIL'] PASSWORD_ADMIN_EMAIL = config.get('PASSWORD_ADMIN_EMAIL', 'XXX')
if 'PASSWORD_ADMIN_PASSWORD' in environ: if 'PASSWORD_ADMIN_PASSWORD' in environ:
PASSWORD_ADMIN_PASSWORD = environ['PASSWORD_ADMIN_PASSWORD'] PASSWORD_ADMIN_PASSWORD = environ['PASSWORD_ADMIN_PASSWORD']
else: else:
# this parameter is mandatory # this parameter is mandatory
PASSWORD_ADMIN_PASSWORD = config['PASSWORD_ADMIN_PASSWORD'] PASSWORD_ADMIN_PASSWORD = config.get('PASSWORD_ADMIN_PASSWORD', 'XXX')
if 'PASSWORD_DEVICE_IDENTIFIER' in environ: if 'PASSWORD_DEVICE_IDENTIFIER' in environ:
PASSWORD_DEVICE_IDENTIFIER = environ['PASSWORD_DEVICE_IDENTIFIER'] PASSWORD_DEVICE_IDENTIFIER = environ['PASSWORD_DEVICE_IDENTIFIER']
else: else:
@ -135,11 +135,11 @@ else:
if 'PKI_ADMIN_PASSWORD' in environ: if 'PKI_ADMIN_PASSWORD' in environ:
PKI_ADMIN_PASSWORD = environ['PKI_ADMIN_PASSWORD'] PKI_ADMIN_PASSWORD = environ['PKI_ADMIN_PASSWORD']
else: else:
PKI_ADMIN_PASSWORD = config['PKI_ADMIN_PASSWORD'] PKI_ADMIN_PASSWORD = config.get('PKI_ADMIN_PASSWORD', 'XXX')
if 'PKI_ADMIN_EMAIL' in environ: if 'PKI_ADMIN_EMAIL' in environ:
PKI_ADMIN_EMAIL = environ['PKI_ADMIN_EMAIL'] PKI_ADMIN_EMAIL = environ['PKI_ADMIN_EMAIL']
else: else:
PKI_ADMIN_EMAIL = config['PKI_ADMIN_EMAIL'] PKI_ADMIN_EMAIL = config.get('PKI_ADMIN_EMAIL', 'XXX')
if 'PKI_URL' in environ: if 'PKI_URL' in environ:
PKI_URL = environ['PKI_URL'] PKI_URL = environ['PKI_URL']
else: else:

View File

@ -12,7 +12,7 @@ except:
from .config import get_config from .config import get_config
from .utils import _, tiramisu_display_name from .utils import _, tiramisu_display_name
from .logger import log from .logger import log
from .dispatcher import dispatcher from .dispatcher import get_dispatcher
from .context import Context from .context import Context
@ -25,7 +25,7 @@ class Controller:
def __init__(self, def __init__(self,
test: bool, test: bool,
) -> None: ) -> None:
pass self.dispatcher = get_dispatcher()
async def call(self, async def call(self,
uri: str, uri: str,
@ -42,7 +42,7 @@ 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 dispatcher.call(version, return await self.dispatcher.call(version,
message, message,
risotto_context, risotto_context,
**kwargs, **kwargs,
@ -58,20 +58,19 @@ 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 dispatcher.publish(version, await self.dispatcher.publish(version,
message, message,
risotto_context, risotto_context,
**kwargs, **kwargs,
) )
@staticmethod @staticmethod
async def check_role(self, async def check_role(uri: str,
uri: str,
username: str, username: str,
**kwargs: dict, **kwargs: dict,
) -> None: ) -> None:
# create a new config # create a new config
async with await Config(dispatcher.option) as config: async with await Config(self.dispatcher.option) as config:
await config.property.read_write() await config.property.read_write()
await config.option('message').value.set(uri) await config.option('message').value.set(uri)
subconfig = config.option(uri) subconfig = config.option(uri)
@ -84,7 +83,7 @@ class Controller:
raise ValueError(_(f'unknown parameter in "{uri}": "{key}"')) raise ValueError(_(f'unknown parameter in "{uri}": "{key}"'))
except ValueOptionError as err: except ValueOptionError as err:
raise ValueError(_(f'invalid parameter in "{uri}": {err}')) raise ValueError(_(f'invalid parameter in "{uri}": {err}'))
await dispatcher.check_role(subconfig, await self.dispatcher.check_role(subconfig,
username, username,
uri, uri,
) )
@ -102,13 +101,14 @@ class TiramisuController(Controller):
if not 'dataset_name' in vars(self): if not 'dataset_name' in vars(self):
raise Exception(f'please specify "dataset_name" to "{self.__class__.__name__}"') 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) self.tiramisu_cache_root_path = join(get_config()['cache']['root_path'], self.dataset_name)
super().__init__(test)
if not test: if not test:
db_conf = get_config()['database']['tiramisu_dsn'] db_conf = get_config()['database']['tiramisu_dsn']
self.save_storage = Storage(engine='postgres') self.save_storage = Storage(engine='postgres')
self.save_storage.setting(dsn=db_conf) self.save_storage.setting(dsn=db_conf)
if self.dataset_name != 'servermodel': if self.dataset_name != 'servermodel':
self.optiondescription = None self.optiondescription = None
dispatcher.set_function('v1.setting.dataset.updated', self.dispatcher.set_function('v1.setting.dataset.updated',
None, None,
TiramisuController.dataset_updated, TiramisuController.dataset_updated,
self.__class__.__module__, self.__class__.__module__,

View File

@ -18,6 +18,9 @@ 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,
@ -492,5 +495,9 @@ class Dispatcher(register.RegisterDispatcher,
return returns return returns
dispatcher = Dispatcher() def get_dispatcher():
register.dispatcher = dispatcher global DISPATCHER
if DISPATCHER is None:
DISPATCHER = Dispatcher()
register.dispatcher = DISPATCHER
return DISPATCHER

View File

@ -7,7 +7,7 @@ except:
from tiramisu import Config, default_storage from tiramisu import Config, default_storage
from .dispatcher import dispatcher from .dispatcher import get_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
@ -70,6 +70,7 @@ 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)
@ -93,6 +94,7 @@ 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
@ -142,6 +144,7 @@ async def api(request,
# 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
@ -167,6 +170,7 @@ async def get_app(loop):
""" build all routes """ build all routes
""" """
global extra_routes, extra_statics global extra_routes, extra_statics
dispatcher = get_dispatcher()
services.link_to_dispatcher(dispatcher) services.link_to_dispatcher(dispatcher)
app = Application(loop=loop) app = Application(loop=loop)
routes = [] routes = []

288
src/risotto/image.py Normal file
View File

@ -0,0 +1,288 @@
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 Image:
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 tmp_dir is None:
tmp_dir = PACKER_TMP_DIRECTORY
self.tmp_dir = tmp_dir
self.parse_applications()
def parse_applications(self) -> None:
self.builds = []
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.builds.append(applicationservice)
def calc_depends(self,
dependencies: list,
appname,
):
app = self.applications[appname]['yml']
if not 'depends' in app or not app['depends']:
return
for dependency in app['depends']:
dependency_path = self.applications[dependency]['path']
if dependency_path not in dependencies:
dependencies.insert(0, dependency_path)
self.calc_depends(dependencies, dependency)
def list_images(self):
print(self.builds)
for build in self.builds:
dependencies = [self.applications[build]['path']]
self.calc_depends(dependencies, build)
yield build, dependencies
def copy_files(self,
src_path: str,
dst_path: str,
) -> None:
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)
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]}
return config
def merge_funcs(self,
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
async def get_packer_information(self,
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(flatten=True)
def do_recipe_checksum(self,
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()
files.append(f'{subpath}/{filename}/ctl_sum')
return sha512('\n'.join(files).encode()).hexdigest()
async def build(self) -> None:
if isdir(self.tmp_dir):
rmtree(self.tmp_dir)
for application, dependencies_path in self.list_images():
packer_tmp_directory = join(self.tmp_dir,
application + '_' + str(time()),
)
makedirs(packer_tmp_directory)
packer_tmp_os_directory = join(packer_tmp_directory, 'os')
makedirs(packer_tmp_os_directory)
packer_tmp_img_directory = join(packer_tmp_directory, 'image')
makedirs(packer_tmp_img_directory)
config = self.load_configuration(dependencies_path, packer_tmp_directory)
self.merge_funcs(config, dependencies_path, packer_tmp_directory)
packer_configuration = await self.get_packer_information(config, packer_tmp_directory)
# OS image needed ?
packer_dst_os_filename = join(self.image_dir,
'os',
packer_configuration['os_name'] + '_' + packer_configuration['os_version'] + '.img',
)
for dependency_path in dependencies_path:
packer_directory = join(dependency_path,
'packer',
'os',
)
self.copy_files(packer_directory,
packer_tmp_os_directory,
)
packer_directory = join(dependency_path,
'packer',
'image',
)
self.copy_files(packer_directory,
packer_tmp_img_directory,
)
if not isfile(packer_dst_os_filename):
self.build_image(packer_dst_os_filename,
packer_tmp_os_directory,
packer_configuration,
)
recipe_checksum = self.do_recipe_checksum(packer_tmp_img_directory)
packer_dst_filename = join(self.image_dir,
f'{recipe_checksum}.img',
)
self.build_image(packer_dst_filename,
packer_tmp_img_directory,
packer_configuration,
)
def build_image(self,
packer_dst_filename: str,
tmp_directory: str,
packer_configuration: dict,
) -> None:
packer_configuration['tmp_directory'] = tmp_directory
recipe = {'variables': packer_configuration}
packer_filename = join(tmp_directory, PACKER_FILE_NAME)
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)
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}'))
move(join(tmp_directory, 'image.img'), packer_dst_filename)
move(join(tmp_directory, 'image.sha256'), f'{packer_dst_filename}.sha256')
rmtree(tmp_directory)
print(_(f'Image {packer_dst_filename} created'))

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')
MESSAGE_TRANSLATION = translation('risotto-message', join(MESSAGE_ROOT_PATH, '..', 'locale')).gettext CUSTOMTYPES = None
MESSAGE_TRANSLATION = None
class DictOption(Option): class DictOption(Option):
@ -593,6 +593,11 @@ 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,
@ -632,6 +637,3 @@ def get_messages(current_module_names,
optiondescriptions, optiondescriptions,
) )
return optiondescriptions_info, root return optiondescriptions_info, root
CUSTOMTYPES = load_customtypes()