diff --git a/inventory.py b/inventory.py new file mode 100644 index 0000000..4ede996 --- /dev/null +++ b/inventory.py @@ -0,0 +1,678 @@ +#!/usr/bin/env python3 + +from argparse import ArgumentParser +from pathlib import Path +from yaml import safe_load +from json import dumps +from traceback import print_exc +from os import environ +import logging + +from rich.tree import Tree +from rich.console import Console +from rich.table import Table +from rich.panel import Panel + +from ansible.parsing.vault import VaultLib, PromptVaultSecret +from ansible.module_utils._text import to_bytes + +from tiramisu import PasswordOption, Config +from tiramisu.error import ValueOptionError, PropertiesOptionError, LeadershipError +from tiramisu.i18n import _ +import rougail +from rougail import Rougail, RougailConfig +from rougail.utils import normalize_family +from rougail.annotator import CONVERT_OPTION + + +NAMESPACE = "socle" +NAMESPACE_HOSTS = "hosts" +VERSION_FILE = None + + +def tiramisu_display_name(kls) -> str: + """Replace the Tiramisu display_name function to display path + description""" + try: + doc = kls.impl_get_information("doc", None) + name = kls.impl_getname() + except AttributeError: + doc = kls.opt.impl_get_information('doc', None) + name = kls.opt.impl_getname() + comment = f" ({doc})" if doc and doc != name else "" + return f"{kls.impl_getpath()}{comment}" + + +rougail.tiramisu_display_name = tiramisu_display_name + + +class DSOFPasswordOption(PasswordOption): + def __init__(self, + *args, + min_len=12, + max_len=None, + forbidden_char=[], + **kwargs): + extra = {} + extra = {'min_len': min_len} + if max_len is not None: + extra['max_len'] = max_len + if forbidden_char: + extra['forbidden_char'] = set(forbidden_char) + super().__init__(*args, extra=extra, **kwargs) + + def validate(self, + value: str) -> None: + super().validate(value) + if len(value) <= self.impl_get_extra('min_len'): + raise ValueError(f'il faut au minimum {self.impl_get_extra("min_len")} caractères') + max_len = self.impl_get_extra('max_len') + if max_len and len(value) > max_len: + raise ValueError(f'il faut au maximum {max_len} caractères') + if self.impl_get_extra("forbidden_char"): + forbidden_char = set(value) & self.impl_get_extra("forbidden_char") + if forbidden_char: + raise ValueError(f'ne doit pas avoir les caracteres speciaux {",".join(forbidden_char)}') + + +CONVERT_OPTION["secret"] = dict(opttype="DSOFPasswordOption") + + +class DSOFRougail(Rougail): + def get_config(self): + """Get Tiramisu Config""" + if not self.config: + tiram_obj = self.converted.save(self.rougailconfig["tiramisu_cache"]) + optiondescription = {} + exec(tiram_obj, {"DSOFPasswordOption": DSOFPasswordOption}, optiondescription) # pylint: disable=W0122 + self.config = Config( + optiondescription["option_0"], + display_name=tiramisu_display_name, + ) + self.config.property.read_write() + for mode in self.rougailconfig['modes_level']: + self.config.permissive.add(mode) + return self.config + + +class Inventory: +############################################### +# Create TIRAMISU object +############################################### + + def __init__(self, args, for_doc=False): + self.args = args + sub_inventory_dir = Path('inventaires') / 'rougail' + self.inside_git_dir = False + inventory_dir = None + if for_doc: + socle_inventory_dir = Path(__file__).parent.parent / 'inventaires' / "rougail" + if socle_inventory_dir.is_dir: + inventory_dir = str(socle_inventory_dir) + if not inventory_dir and not sub_inventory_dir.is_dir(): + if self.args.debug: + print(f'cannot find {sub_inventory_dir}') + for git_file in [Path.cwd().parent / 'inventaires' / "rougail", + Path.cwd() / 'packaging' / 'inventaires' / "rougail", + ]: + if git_file.is_dir(): + if self.args.debug: + print('inside git dir !') + self.inside_git_dir = True + inventory_dir = git_file + break + if not inventory_dir: + inventory_dir = sub_inventory_dir + RougailConfig['dictionaries_dir'] = [inventory_dir] + RougailConfig['variable_namespace'] = NAMESPACE + RougailConfig['extra_dictionaries'][NAMESPACE_HOSTS] = ['inventaires/hosts/'] + if for_doc: + project_name = Path().cwd().name.rsplit('-', 1)[0] + versions = {project_name: {'docker_version': None}} + else: + with VERSION_FILE.open() as fh: + versions = safe_load(fh) + for project, version in versions.items(): + if "docker_version" not in version: + continue + project_name = self.get_project_name(project) + # get the first directory in ~/DSOF-SIx-xxxx// + if for_doc: + root_path = Path.cwd() / 'packaging' + elif self.inside_git_dir: + root_path = Path.cwd().parent.parent.parent / (project + '-packaging') / 'packaging' + else: + root_path = Path.home() / project / version['packaging_version'] + if root_path.is_dir(): + root_path = next(root_path.iterdir()) + path = root_path / sub_inventory_dir + if path.is_dir(): + RougailConfig['extra_dictionaries'][project_name] = [str(path)] + elif self.args.debug: + print(f'cannot find {path}') + #FIXME + #RougailConfig['tiramisu_cache'] = 'socle.py' + RougailConfig['functions_file'] = 'Rougail/functions.py' + self.errors = [] + self.warnings = [] + self.console = Console(force_terminal=True) + + def get_project_name(self, project): + return normalize_family(project.split('-', 2)[-1]) + + def load(self): + rougail = DSOFRougail() + self.conf = rougail.get_config() + self.conf.property.read_write() + self.objectspace = rougail.converted +# self.objectspace.annotate() + +############################################### +# Read Ops file +############################################### + + def load_custom(self): + if not CUSTOM_FILE or not CUSTOM_FILE.is_file(): + if self.args.debug: + print(f'cannot find {CUSTOM_FILE}') + return + prompt = PromptVaultSecret(INVENTORY_PASSWORD, 'default', ["Vault password: "]) + if not INVENTORY_PASSWORD: + prompt.load() + vault = VaultLib([('default', prompt)]) + with CUSTOM_FILE.open('rb') as fh: + values = safe_load(vault.decrypt(fh.read())) + for key, value in values.items(): + self.read_inventory(self.conf, + key, + value, + CUSTOM_FILE, + ) + + def set_env(self): + if not CURRENT_ENV and self.inside_git_dir: + current_env = 'git' + else: + current_env = CURRENT_ENV + if not current_env: + self.errors.append(f"la variable d'environnement \"current_env\" est obligatoire") + return + self.conf.option(f"socle.env_name").value.set(current_env) + + def set_versions(self): + if self.errors: + return + with VERSION_FILE.open() as fh: + versions = safe_load(fh) + for project, version in versions.items(): + project_name = self.get_project_name(project) + if "docker_version" not in version: + continue + try: + self.conf.option(f"{project_name}.compose.image_tag").value.set(version["docker_version"]) + except AttributeError: + pass + + def read_inventory(self, + conf, + key, + value, + file, + *, + index=None, + ): + sub_conf = conf.option(key, index) + try: + isoptiondescription = sub_conf.isoptiondescription() + except AttributeError as err: + # ugly + try: + try: + paths, options = conf.get()._children + except: + paths, options = conf.get().opt._children + if '{{ suffix }}' in paths: + option = options[paths.index('{{ suffix }}')] + path = option._suffixes.params.args[0].option._path + # ugly, assume that dynamic variable are in parent family + if '{{ suffix }}' in path: + cnt = path.count('.') + sub_path = sub_conf._path + subcnt = sub_path.count('.') + path = sub_path.rsplit('.', subcnt - cnt + 1)[0] + '.' + path.rsplit('.', 1)[-1] + values = self.conf.option(path).value.get() + key = normalize_family(key) + values.append(key) + self.conf.option(path).value.set(values) + self.read_inventory(conf, key, value, file, index=index) + return + except Exception: + if args.debug: + print_exc() + pass + + if conf == self.conf: + path = NAMESPACE + else: + path = conf.path() + self.warnings.append(f'"{key}" est inconnu dans "{path}" mais est défini dans "{file}"') + return + except LeadershipError as err: + if args.debug: + print_exc() + self.errors.append(str(err)) + return + if isoptiondescription: + if not sub_conf.isleadership(): + if not isinstance(value, dict): + print('pffff1') + return + for sub_key, sub_value in value.items(): + self.read_inventory(sub_conf, + sub_key, + sub_value, + file, + ) + else: + if not isinstance(value, list): + return + leader_option = sub_conf.leader().name() + leader_value = [] + for leader in value: + if leader_option not in leader: + self.warnings.append(f'cannot find leader "{sub_conf.leader().path()} in {list(leader)}') + return + leader_value.append(leader[leader_option]) + self.read_inventory(sub_conf, + leader_option, + leader_value, + file, + ) + for idx, sub_value in enumerate(value): + for sub_key, sub_value in sub_value.items(): + if sub_key == leader_option: + continue + self.read_inventory(sub_conf, + sub_key, + sub_value, + file, + index=idx, + ) + else: + try: +# sub_conf.permissive.set(frozenset(['advanced'])) + sub_conf.value.set(value) + except ValueOptionError as err: + if args.debug: + print_exc() + self.errors.append(str(err).replace('"', "'")) + except PropertiesOptionError as err: + if args.debug: + print_exc() + self.warnings.append(f'"{err}" mais est défini dans "{file}"') + + +############################################### +# Host +############################################### + + def get_hosts(self): + ret = {"_meta": {"hostvars": {}}, "all": {"children": ["ungrouped"]}} + if self.errors: + ret["_meta"]["hostvars"]["localhost"] = {'_errors': self.errors} + ret["ungrouped"] = {"hosts": ["localhost"]} + else: + inventories = {} + self.conf.property.read_only() + for line, value in self.conf.value.get().items(): + self.parse_line(line, + line, + '', + value, + inventories, + False, + False, + ) + if NAMESPACE_HOSTS not in inventories: + return ret + hostnames = inventories[NAMESPACE_HOSTS]['hostnames'] + ret_hosts = {} + for name, hosts in hostnames.items(): + if 'hosts' in hosts: + for idx, host in enumerate(hosts['hosts']): + index = str(idx + 1) + if idx < 9: + index = '0' + index + host_name = hosts['prefix_name'] + index + ret_hosts.setdefault(name, {})[host_name] = host + ret.setdefault(name, {}).setdefault('hosts', []).append(host_name) + else: + ret["all"]["children"].append(name) + ret[name] = hosts + for hosts in ret_hosts.values(): + for host, domain_name in hosts.items(): + ret['_meta']['hostvars'][host] = {'ansible_host': domain_name} + ret['_meta']['hostvars'][host].update(inventories) + #if CURRENT_NAMESPACE != NAMESPACE: + # ret['_meta']['hostvars'][host][CURRENT_NAMESPACE] = inventories[CURRENT_NAMESPACE] + # print(ret_hosts) + # ret['_meta']['hostvars']['localhost'] = inventories + return ret + + def display_hosts(self): + self.conf.property.read_only() + hostnames = self.conf.option(NAMESPACE_HOSTS).option('hostnames') + ret_hosts = {} + for optiondescription in hostnames.list('optiondescription'): + hosts = optiondescription.value.get() + try: + prefix_name = optiondescription.option('prefix_name').value.get() + except AttributeError: + # it's a group + continue + for idx, host in enumerate(optiondescription.option('hosts').value.get()): + index = str(idx + 1) + if idx < 9: + index = '0' + index + host_name = prefix_name + index + ret_hosts[host_name] = host + + print() + header = Table(show_header=False, title="Liste des adresses") + for key, value in ret_hosts.items(): + header.add_row(key, value) + self.console.print(header) +############################################### +# Search unspecified mandatories variables +############################################### + + def mandatory(self): + title = False + options_with_error = [] + for option in self.conf.value.mandatory(): + try: + option.value.get() + if not title: + self.errors.append("Les variables suivantes sont obligatoires mais n'ont pas de valeur :") + title = True + self.errors.append(f' - {option.doc()}') + except PropertiesOptionError: + options_with_error.append(option) + if not title: + for idx, option in enumerate(options_with_error): + if not idx: + self.errors.append("Les variables suivantes sont inaccessibles mais sont vides :") + self.errors.append(f' - {option.doc()}') + +############################################### +# Tiramisu to inventory +############################################### + + def rich(self, ret, tree): + for key, value in ret.items(): + if isinstance(value, dict): + subtree = tree.add(f":open_file_folder: {key}", + guide_style="bold bright_blue", + ) + self.rich(value, subtree) + elif isinstance(value, list): + subtree = tree.add(f":notebook: {key} :", + guide_style="bold bright_blue", + ) + for val in value: + subtree.add(str(val)) + else: + tree.add(f":notebook: {key} : {value}") + + def rich_display(self): + header_variable = 'Variable\n' + header_variable += '[bright_blue]Variable non documentée[/bright_blue]\n' + header_variable += '[red1]Variable non documentée mais modifiée[/red1]' + if self.args.read_only: + header_variable += '\n[orange1]Variable non modifiable[/orange1]' + header_value = '[gold1]Valeur par défaut[/gold1]\n' + header_value += 'Valeur modifiée\n' + header_value += '([red1]Valeur par défaut originale[/red1])' + header = Table.grid(padding=1, collapse_padding=True) + header.pad_edge = False + header.add_row(header_variable, header_value) + header = Panel.fit(header, title="Légende") + self.console.print(header) + inventories = {} + if self.args.read_only: + self.conf.property.read_only() + else: + self.conf.property.read_write() + for line, value in self.conf.value.get().items(): + self.parse_line(line, + line, + '', + value, + inventories, + False, + False, + for_doc=True, + ) + for warning in self.warnings: + self.console.print(Tree(f":warning: {warning}")) + if self.errors: + self.display_errors() + tree = Tree(":open_file_folder: Inventaire", + guide_style="bold bright_blue", + ) + self.rich(inventories, tree) + self.console.print(tree) + self.display_hosts() + + def display_errors(self): + tree = Tree(":stop_sign: ERREURS", + guide_style="bold bright_red", + ) + for error in self.errors: + tree.add(error) + self.console.print(tree) + exit(1) + + def parse_line(self, + full_path, + line, + parent_path, + value, + dico, + leadership, + family_hidden, + *, + for_doc=False, + ): + if '.' in line: + # it's a dict + family, variable = line.split('.', 1) + current_path = parent_path + if current_path: + current_path += '.' + current_path += family + if for_doc: + if 'hidden' in self.conf.option(current_path).property.get() or family_hidden: + family_hidden = True + family = f'[orange1]{family}[/orange1]' + elif 'advanced' in self.conf.option(current_path).property.get(): + family = f'[bright_blue]{family}[/bright_blue]' + if '.' not in variable and self.conf.option(full_path.rsplit('.', 1)[0]).isleadership(): + dico.setdefault(family, []) + leadership = True + else: + dico.setdefault(family, {}) + leadership = False + self.parse_line(full_path, + variable, + current_path, + value, + dico[family], + leadership, + family_hidden, + for_doc=for_doc, + ) + elif leadership: + # it's a leadership + for idx, val in enumerate(value): + dic = {k.rsplit('.', 1)[-1]: v for k, v in val.items()} + if for_doc: + leader = True + for k, v in val.items(): + if leader: + is_default = self.conf.option(k).owner.isdefault() + properties = self.conf.option(k).property.get() + else: + is_default = self.conf.option(k, idx).owner.isdefault() + properties = self.conf.option(k, idx).property.get() + if self.conf.option(k).type() == _('password') and not self.args.show_password: + v = "*" * 10 + subpath = k.rsplit('.', 1)[-1] + if 'hidden' in properties or family_hidden: + subpath = f'[orange1]{subpath}[/orange1]' + elif 'advanced' in properties: + if isdefault: + subpath = f'[bright_blue]{subpath}[/bright_blue]' + else: + subpath = f'[red1]{subpath}[/red1]' + if is_default: + v = '[gold1]' + str(v) + '[/gold1]' + dico.append(f'{subpath}: {v}') + leader = False + else: + dico.append(dic) + else: + # it's a variable + is_default = self.conf.option(full_path).owner.isdefault() + default_value = None + if for_doc: + mod_is_red = False + if not is_default: + true_default_value = self.conf.option(full_path).value.default() + if true_default_value and true_default_value != value: + if isinstance(true_default_value, list): + default_value = [f' ([red1]{true}[/red1])' for true in true_default_value] + else: + default_value = f' ([red1]{true_default_value}[/red1])' + mod_is_red = True + if self.conf.option(full_path).type() == _('password') and not self.args.show_password: + if isinstance(value, list): + value = ["*" * 10 for val in value] + else: + value = "*" * 10 + if 'hidden' in self.conf.option(full_path).property.get() or family_hidden: + line = f'[orange1]{line}[/orange1]' + elif 'advanced' in self.conf.option(full_path).property.get(): + if not mod_is_red: + line = f'[bright_blue]{line}[/bright_blue]' + else: + line = f'[red1]{line}[/red1]' + if is_default: + if isinstance(value, list): + dico[line] = ['[gold1]' + str(val) + '[/gold1]' for val in value] + else: + dico[line] = '[gold1]' + str(value) + '[/gold1]' + if (for_doc and not is_default) or not for_doc: + if default_value: + if isinstance(value, list): + len_value = len(value) + len_default_value = len(default_value) + len_values = max(len_value, len_default_value) + new_value = [] + for idx in range(len_values): + new = '' + if idx < len_value: + new += value[idx] + if idx < len_default_value: + new += default_value[idx] + new_value.append(new) + value = new_value + else: + value = str(value) + default_value + dico[line] = value + + +def main(args): + inventory = Inventory(args) + inventory.load() + inventory.load_custom() + inventory.set_env() + inventory.set_versions() + if inventory.errors: + inventory.display_errors() + if args.hosts: + inventory.display_hosts() + elif args.list: + print(dumps(inventory.get_hosts(), ensure_ascii=False, indent=2) + ) + else: + if not args.no_mandatory: + inventory.mandatory() + if args.host: + # inventory is already set during --list + print(dumps({})) + else: + inventory.rich_display() + + +def get_argsparse(): + parser = ArgumentParser() + parser.add_argument('--list', action='store_true') + parser.add_argument('--host', action='store') + parser.add_argument('--hosts', action='store_true') + parser.add_argument('--debug', action='store_true') + parser.add_argument('--read_only', action='store_true') + parser.add_argument('--no_mandatory', action='store_true') + parser.add_argument('--show_password', action='store_true') + args = parser.parse_args() + if args.debug: + level = logging.DEBUG + logging.basicConfig( + level=level, + format='%(asctime)s - %(levelname)s - %(message)s' + ) + return args + + +if __name__ == "__main__": + if 'CURRENT_ENV' not in environ: + CURRENT_ENV = environ.get('current_env') + else: + CURRENT_ENV = environ['CURRENT_ENV'] + if 'VERSION_FILE' not in environ: + VERSION_FILE = Path('versions.yml') + else: + VERSION_FILE = Path(environ['VERSION_FILE']) + if 'INVENTORY_PASSWORD' in environ: + INVENTORY_PASSWORD = environ['INVENTORY_PASSWORD'] + INVENTORY_PASSWORD = to_bytes(INVENTORY_PASSWORD, errors='strict', nonstring='simplerepr').strip() + else: + INVENTORY_PASSWORD = None + + if CURRENT_ENV: + CUSTOM_FILE = Path.cwd().parent.parent / 'inventaires' / CURRENT_ENV / "custom.yml" + else: + CUSTOM_FILE = Path("custom.yml") + + if not VERSION_FILE.is_file(): + raise Exception(f'cannot find VERSION_FILE "{VERSION_FILE}"') + + try: + args = get_argsparse() + except Exception as err: + print(dumps({'_errors': str(err)}, ensure_ascii=False, indent=2)) + exit(1) + try: + main(args) + except Exception as err: + if args.debug: + print_exc() + if args.list or args.host: + print(dumps({'_errors': str(err)}, ensure_ascii=False, indent=2)) + exit(1) + else: + tree = Tree(":stop_sign: ERREURS", + guide_style="bold bright_red", + ) + tree.add(str(err)) + Console().print(tree) + exit(1) diff --git a/src/rougail/user_data_file/__init__.py b/src/rougail/user_data_file/__init__.py new file mode 100644 index 0000000..0011de3 --- /dev/null +++ b/src/rougail/user_data_file/__init__.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 + +from rougail import RougailConfig +from ruamel.yaml import YAML +from tiramisu.error import ValueOptionError, PropertiesOptionError, LeadershipError + + +class RougailUserDataFile: + def __init__(self, + conf, + *, + user_datas=None, + rougailconfig=None, + ) -> None: + if rougailconfig is None: + rougailconfig = RougailConfig + self.rougailconfig = rougailconfig + self.filename = self.rougailconfig['file.filename'] + self.yaml = YAML() + self.conf = conf + if user_datas: + self.errors = user_datas['errors'] + self.warnings = user_datas['warnings'] + else: + self.errors = [] + self.warnings = [] + + + def read(self, + ) -> None: + with open(self.filename) as fh_config: + values = self.yaml.load(fh_config) + if not values: + return + for key, value in values.items(): + self.parse(self.conf, + key, + value, + ) + + def parse(self, + conf, + key, + value, + *, + index=None, + ): + try: + sub_conf = conf.option(key, index) + except AttributeError as err: + self.errors.append(str(err)) + return +# try: +# isoptiondescription = sub_conf.isoptiondescription() +# except AttributeError as err: +## if args.debug: +## print_exc() +# if index is not None: +# msg = f'cannot find {sub_conf.path()} with index {index}' +# else: +# msg = f'cannot find {sub_conf.path()}' +# self.errors.append(msg) +# return +## # ugly +## try: +## try: +## paths, options = conf.get()._children +## except: +## paths, options = conf.get().opt._children +## if '{{ suffix }}' in paths: +## option = options[paths.index('{{ suffix }}')] +## path = option._suffixes.params.args[0].option._path +## # ugly, assume that dynamic variable are in parent family +## if '{{ suffix }}' in path: +## cnt = path.count('.') +## sub_path = sub_conf._path +## subcnt = sub_path.count('.') +## path = sub_path.rsplit('.', subcnt - cnt + 1)[0] + '.' + path.rsplit('.', 1)[-1] +## values = self.conf.option(path).value.get() +## key = normalize_family(key) +## values.append(key) +## self.conf.option(path).value.set(values) +## self.parse(conf, key, value, index=index) +## return +## except Exception: +## if args.debug: +## print_exc() +## pass +## +## if conf == self.conf: +## path = NAMESPACE +## else: +## path = conf.path() +## self.warnings.append(f'"{key}" est inconnu dans "{path}" mais est défini dans "{self.filename}"') +## return + except LeadershipError as err: +# if args.debug: +# print_exc() + self.errors.append(str(err)) + return + if sub_conf.isoptiondescription(): + return self.parse_optiondescription(sub_conf, + value, + ) + self.load(sub_conf, + value, + ) + + def load(self, + conf, + value, + ): + try: +# sub_conf.permissive.set(frozenset(['advanced'])) + conf.value.set(value) + except ValueOptionError as err: +# if args.debug: +# print_exc() + self.errors.append(str(err).replace('"', "'")) + except PropertiesOptionError as err: +# if args.debug: +# print_exc() + self.warnings.append(f'"{err}" but is defined in "{self.filename}"') + + def parse_optiondescription(self, + conf, + value, + ) -> None: + if conf.isleadership(): + return self.parse_leadership(value, + conf, + ) + if not isinstance(value, dict): + self.warnings.append(f'invalid value "{value}" for the optiondescription {conf.path()}') + return + for sub_key, sub_value in value.items(): + self.parse(conf, + sub_key, + sub_value, + ) + + def parse_leadership(self, + value, + conf, + ): + if not isinstance(value, list): + self.warnings.append(f'invalid value "{value}" for the leadership {conf.path()}') + return + leader_name = conf.leader().name() + leader_value = [] + for leader in value: + if leader_name not in leader: + self.warnings.append(f'cannot find value for the leader "{conf.leader().path()} in {list(leader)}') + return + #leader_value.append(leader[leader_name]) + leader_value.append(leader.pop(leader_name)) + self.parse(conf, + leader_name, + leader_value, + ) + for idx, sub_value in enumerate(value): + for sub_key, sub_value in sub_value.items(): + # if sub_key == leader_name: + # continue + self.parse(conf, + sub_key, + sub_value, + index=idx, + ) diff --git a/src/rougail/user_data_file/cli.py b/src/rougail/user_data_file/cli.py new file mode 100644 index 0000000..039b398 --- /dev/null +++ b/src/rougail/user_data_file/cli.py @@ -0,0 +1,36 @@ +""" +Cli code for Rougail-user-data-file + +Silique (https://www.silique.fr) +Copyright (C) 2024 + +distribued with GPL-2 or later license + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" +from . import RougailUserDataFile + + +def run(rougailconfig, + config, + user_datas, + ): + RougailUserDataFile(config, + user_datas=user_datas, + rougailconfig=rougailconfig, + ).read() + + +__all__ = ('run',) diff --git a/src/rougail/user_data_file/config.py b/src/rougail/user_data_file/config.py new file mode 100644 index 0000000..f79ccca --- /dev/null +++ b/src/rougail/user_data_file/config.py @@ -0,0 +1,56 @@ +""" +Config file for Rougail-user-data + +Silique (https://www.silique.fr) +Copyright (C) 2024 + +distribued with GPL-2 or later license + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" +# from .utils import _ + + +def get_rougail_config(*, + backward_compatibility=True, + ) -> dict: + options = """ +file: + description: Configuration rougail-user-data-file + disabled: + type: jinja + jinja: | + {% if 'file' not in step.user_data %} + disabled + {% endif %} + filename: + description: Filename with user data + alternative_name: ff + type: unix_filename + params: + allow_relative: True + test_existence: True + types: + - file +""" + return {'name': 'file', + 'process': 'user data', + 'options': options, + 'level': 50, + } + + +__all__ = ('get_rougail_config') +