From 43de83cce776cedc3b5e0b6488617e97b52036f3 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Fri, 2 Apr 2021 09:16:53 +0200 Subject: [PATCH 1/2] add cucciaiata-import --- provisioning.yaml | 82 +++++++++++ provisioning_example.sh | 13 +- {script => scripts}/cucchiaiata-cli | 3 +- scripts/cucchiaiata-import | 218 ++++++++++++++++++++++++++++ setup.py | 2 +- src/cucchiaiata/common.py | 37 +++++ src/cucchiaiata/configuration.py | 4 +- src/cucchiaiata/parser.py | 9 -- 8 files changed, 345 insertions(+), 23 deletions(-) create mode 100755 provisioning.yaml rename {script => scripts}/cucchiaiata-cli (80%) create mode 100755 scripts/cucchiaiata-import diff --git a/provisioning.yaml b/provisioning.yaml new file mode 100755 index 0000000..f6866ee --- /dev/null +++ b/provisioning.yaml @@ -0,0 +1,82 @@ +zones: + - name: internet + settings: + configuration: + network: 192.168.1.0/24 + host_start: 192.168.1.10 + host_stop: 192.168.1.100 + dns: [192.168.1.2] + gateway: 192.168.1.254 + +clusters: + - name: cluster.cadoles.com + zone: internet + settings: + configuration: + network: + virtual_ip: 192.168.1.1 + nodes: + - name: node1.cadoles.com + zones: [internet] + +servermodels: + - name: unbound + applicationservices: [unbound] + settings: + configuration: + serveur_dns: + unbound_local_zones: cadoles.com + unbound_allowed_client_cidr: 192.168.1.0/24 + reseau: + unbound_route_address: 192.168.1.254 + unbound: + unbound_zone_cadoles_com: + hostname_cadoles_com: + hostname_cadoles_com: + - toto + - titi + ip_cadoles_com: + - index: 0 + value: 192.168.1.25 + type_cadoles_com: + - index: 1 + value: CNAME + cname_cadoles_com: + - index: 1 + value: toto.cadoles.com + - name: aca + applicationservices: [openssh-server] + settings: + configuration: + serveur_ssh: + ssh_allow_networks: admin.cadoles.com + children: + - name: etb1 + children: + - name: unbound_etab1 + other_parents: [unbound] + settings: + configuration: + reseau: + unbound_domain_name: dns.cadoles.com + +organizations: + - name: cadoles.com + sites: + - name: etab1 + zones: [internet] + servers: + - name: dns.cadoles.com + cluster: cluster.cadoles.com + zones: [internet] + servermodel: unbound_etab1 + settings: + configuration: + reseau: + unbound_ip_address_cidr: 192.168.1.2/24 + opennebula: + cpu: 0.2 + vcpu: 2 + memory: 2 + - name: etab2 + zones: [internet] diff --git a/provisioning_example.sh b/provisioning_example.sh index cc81d2b..591b5e4 100755 --- a/provisioning_example.sh +++ b/provisioning_example.sh @@ -53,7 +53,7 @@ cucchiaiata-cli v1.setting.session.stop --session_id "$S" --save cucchiaiata-cli v1.infra.cluster.create --cluster_name cluster.cadoles.com \ --zone_name internet cucchiaiata-cli v1.infra.cluster.node.create --node_name node1.cadoles.com \ - --cluster_name cluster.cadoles.com \ + --cluster_name cluster.cadoles.com \ --zones_name internet # configuration @@ -82,17 +82,14 @@ cucchiaiata-cli v1.infra.site.create --site_name etab2 \ # Servermodels # -> base-fedora-32 (Cadoles) -# |-> sm_cluster (servermodel) -# | # '-> unbound (Cadoles) # '-> unbound (servermodel) ----, # | # -> openssh-server (Cadoles) |--> unbound_etab1 (servermodel) # '-> aca (servermodel) | # '-> etab1 (servermodel) ------' -cucchiaiata-cli v1.setting.servermodel.create --servermodel_name sm_cluster --applicationservices base-fedora-32 -cucchiaiata-cli v1.setting.servermodel.create --servermodel_name unbound --applicationservices unbound -cucchiaiata-cli v1.setting.servermodel.create --servermodel_name aca --applicationservices openssh-server +cucchiaiata-cli v1.setting.servermodel.create --servermodel_name unbound --applicationservices_name unbound +cucchiaiata-cli v1.setting.servermodel.create --servermodel_name aca --applicationservices_name openssh-server cucchiaiata-cli v1.setting.servermodel.create --servermodel_name etab1 --parents_name aca cucchiaiata-cli v1.setting.servermodel.create --servermodel_name unbound_etab1 --parents_name etab1 unbound @@ -150,8 +147,8 @@ cucchiaiata-cli v1.setting.session.stop --session_id "$S" --save #======================================================================================================= # Generate configuration -cucchiaiata-cli v1.setting.config.configuration.server.deploy --server dns.cadoles.com -cucchiaiata-cli v1.setting.template.generate --server dns.cadoles.com +cucchiaiata-cli v1.setting.config.configuration.server.deploy --server_name dns.cadoles.com +cucchiaiata-cli v1.setting.template.generate --server_name dns.cadoles.com # Generate cluster's configurtion #cucchiaiata-cli v1.provider.configure -c cluster.cadoles.com diff --git a/script/cucchiaiata-cli b/scripts/cucchiaiata-cli similarity index 80% rename from script/cucchiaiata-cli rename to scripts/cucchiaiata-cli index 269b2e8..188d2d0 100755 --- a/script/cucchiaiata-cli +++ b/scripts/cucchiaiata-cli @@ -10,8 +10,7 @@ from cucchiaiata.i18n import _ def main(): try: - if len(argv) > 2 and argv[1] in ['v1.setting.session.configure', - 'v1.setting.session.configure']: + if len(argv) > 2 and argv[1] == 'v1.setting.session.configure': Configuration().get() else: parser = Parser() diff --git a/scripts/cucchiaiata-import b/scripts/cucchiaiata-import new file mode 100755 index 0000000..f456bc2 --- /dev/null +++ b/scripts/cucchiaiata-import @@ -0,0 +1,218 @@ +#!/usr/bin/python3 +"""Zephir-cmd-input script +""" +from sys import exit, argv +from yaml import load, SafeLoader, YAMLError +from time import sleep +from os.path import isfile + +from cucchiaiata import Configuration +from cucchiaiata.common import Common +from cucchiaiata.i18n import _ + + + +class Import(Common): + def __init__(self, + config_file: str, + ) -> None: + super().__init__() + with open(config_file, 'r') as stream: + try: + self.config = load(stream, + Loader=SafeLoader, + ) + except YAMLError as err: + raise Exception(_('unable to lead the YAML file {}').format(err)) + + self.remote_config = self.remote_json_to_config(self.cucchiaiata_config.remote_url) + + def parse_zones(self) -> None: + for zone in self.config.get('zones', []): + self.configuration('infra', + 'zone', + zone, + ) + + def parse_clusters(self) -> None: + for cluster in self.config.get('clusters', []): + self.configuration('infra', + 'cluster', + cluster, + zone_name=cluster['zone'], + ) + for node in cluster.get('nodes', []): + self.configuration('infra', + 'cluster.node', + node, + cluster_name=cluster['name'], + zones_name=node['zones'], + ) + + def parse_servermodels(self) -> None: + for servermodel in self.config.get('servermodels', []): + self.add_servermodel(servermodel) + + def parse_organizations(self) -> None: + for organization in self.config.get('organizations', []): + self.configuration('infra', + 'organization', + organization, + ) + for site in organization.get('sites', []): + self.configuration('infra', + 'site', + site, + organization_name=organization['name'], + zones_name=site['zones'], + ) + for server in site.get('servers', []): + self.configuration('infra', + 'server', + server, + site_name=site['name'], + cluster_name=server['cluster'], + zones_name=server['zones'], + servermodel_name=server['servermodel'], + ) + self.send('v1.setting.config.configuration.server.deploy', + server_name=server['name'], + ) + self.send('v1.setting.template.generate', + server_name=server['name'], + ) + + def configuration(self, + domain: str, + element: str, + dico: dict, + **kwargs, + ) -> None: + self.upset_element(domain, + element, + dico['name'], + kwargs, + ) + if 'settings' in dico: + #FIXME + sleep(1) + self.apply_settings(element, + dico, + ) + + def apply_settings(self, + element: str, + dico: dict, + ) -> None: + session_id = self.send(f'v1.setting.session.{element}.start', + **{f'{element}_name': dico['name']}, + )['session_id'] + config = Configuration() + config.message = 'v1.setting.session.configure' + config.session_id = session_id + tiramisu = None + tiramisu_namespace = None + for key, value in self.settings_paths(dico['settings']): + current_namespace = key.split('.', 1)[0] + if tiramisu_namespace != current_namespace: + if tiramisu is not None: + self.send_configuration(tiramisu, + session_id, + ) + tiramisu_namespace = current_namespace + self.send('v1.setting.session.filter', + session_id=session_id, + namespace=tiramisu_namespace, + ) + tiramisu = config.configure_server() + try: + if tiramisu.option(key).option.isfollower(): + for val in value: + tiramisu.option(key, val['index']).value.set(val['value']) + else: + if tiramisu.option(key).option.ismulti() and \ + not isinstance(value, list): + value = [value] + tiramisu.option(key).value.set(value) + except ValueError as err: + print(_(f'error when setting "{domain}" "{dico["name"]}": "{key}" with value "{value}": {err}')) + exit(1) + except Exception as err: + print(_(f'unexpected error when setting "{domain}" "{dico["name"]}": "{key}" with value "{value}": {err}')) + exit(1) + self.send_configuration(tiramisu, + session_id, + ) + self.send('v1.setting.session.stop', + session_id=session_id, + save=True, + ) + + def upset_element(self, + domain: str, + element: str, + name: str, + kwargs: dict, + ) -> None: + message_name = element + if '.' in message_name: + message_name = message_name.rsplit('.', 1)[-1] + try: + self.send(f'v1.{domain}.{element}.describe', + **{f'{message_name}_name': name}, + ) + except: + # not exists + print(f'add "{element}" "{name}"') + kwargs[f'{message_name}_name'] = name + self.send(f'v1.{domain}.{element}.create', + **kwargs, + ) + + def settings_paths(self, + dico: dict, + subpath: str=None, + ) -> list: + ret = [] + for key, value in dico.items(): + if subpath: + key_path = f'{subpath}.{key}' + else: + key_path = key + if isinstance(value, dict): + ret.extend(self.settings_paths(value, key_path)) + else: + ret.append((key_path, value)) + return ret + + def add_servermodel(self, + servermodel: dict, + parents: list=[], + ) -> None: + if 'other_parents' in servermodel: + parents = parents.copy() + parents.extend(servermodel['other_parents']) + self.configuration('setting', + 'servermodel', + servermodel, + parents_name=parents, + applicationservices_name=servermodel.get('applicationservices', []), + ) + if 'children' in servermodel: + child_parents = parents.copy() + child_parents.append(servermodel['name']) + for child in servermodel['children']: + self.add_servermodel(child, + child_parents, + ) + + +if __name__ == "__main__": + if len(argv) != 2 or not isfile(argv[1]): + print(_(f'usage: {argv[0]} filename.yaml')) + exit(1) + imp = Import(argv[1]) + imp.parse_zones() + imp.parse_clusters() + imp.parse_servermodels() + imp.parse_organizations() diff --git a/setup.py b/setup.py index 6a18f53..8ca5585 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,6 @@ setup( name='cucchiaiata', version='0.1', packages=['cucchiaiata' ], - scripts=['script/cucchiaiata-cli'], + scripts=['scripts/cucchiaiata-cli', 'scripts/cucchiaiata-import'], package_dir={"": "src"}, ) diff --git a/src/cucchiaiata/common.py b/src/cucchiaiata/common.py index c0fa6a5..716160e 100644 --- a/src/cucchiaiata/common.py +++ b/src/cucchiaiata/common.py @@ -48,6 +48,43 @@ class Common: json = req.json() return config_type(json) + def get_payload(self, + message: str): + # remove symlinkoption and default value from payload + payload = {} + for option in self.remote_config.option(message).list(): + if not option.owner.isdefault() and not option.option.issymlinkoption(): + payload[option.option.name()] = option.value.get() + return payload + + def send(self, + message: str, + **kwargs, + ) -> None: + self.remote_config.option('message').value.set(message) + for key, value in kwargs.items(): + self.remote_config.option(f'{message}.{key}').value.set(value) + payload = self.get_payload(message) + return send_data(message, + payload, + ) + + def send_configuration(self, + tiramisu: 'ConfigAPI', + session_id: str, + ) -> None: + tiramisu.send() + try: + self.send('v1.setting.session.validate', + session_id=session_id, + ) + except Exception as err: + self.send('v1.setting.session.stop', + session_id=session_id, + ) + print(_(f'error when validate setting to "{name}" "{dico["name"]}": {err}')) + exit(1) + def send_data(uri: str, payload: Dict, diff --git a/src/cucchiaiata/configuration.py b/src/cucchiaiata/configuration.py index ee998be..60fb015 100644 --- a/src/cucchiaiata/configuration.py +++ b/src/cucchiaiata/configuration.py @@ -25,9 +25,7 @@ class ConfigAPI(Config): class Configuration(Common): def configure_server(self): - smessage = self.message.split('.') - version = smessage[0] - type = smessage[-2] + version = self.message.split('.', 1)[0] url = '{}/{}/setting/{}'.format(self.cucchiaiata_config.remote_url, version, self.session_id, diff --git a/src/cucchiaiata/parser.py b/src/cucchiaiata/parser.py index 4f2f27a..f01e90b 100644 --- a/src/cucchiaiata/parser.py +++ b/src/cucchiaiata/parser.py @@ -36,12 +36,3 @@ class Parser(Common): # send message return send_data(message, payload) - - def get_payload(self, - message: str): - # remove symlinkoption and default value from payload - payload = {} - for option in self.remote_config.option(message).list(): - if not option.owner.isdefault() and not option.option.issymlinkoption(): - payload[option.option.name()] = option.value.get() - return payload From 6dcb5b3bf46f7f933f21711eca02fa31943c722c Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Fri, 2 Apr 2021 09:22:09 +0200 Subject: [PATCH 2/2] remove scripts in setup.py --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 8ca5585..bafe9fa 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,5 @@ setup( name='cucchiaiata', version='0.1', packages=['cucchiaiata' ], - scripts=['scripts/cucchiaiata-cli', 'scripts/cucchiaiata-import'], package_dir={"": "src"}, )