feat: add rougail secret_manager

This commit is contained in:
egarette@silique.fr 2025-03-19 11:43:04 +01:00
parent d35fe16cd9
commit 999b53889c
53 changed files with 652 additions and 64 deletions

View file

@ -43,7 +43,7 @@ def tiramisu_display_name(
"""Replace the Tiramisu display_name function to display path + description""" """Replace the Tiramisu display_name function to display path + description"""
doc = kls._get_information(subconfig, "doc", None) doc = kls._get_information(subconfig, "doc", None)
comment = f" ({doc})" if doc and doc != kls.impl_getname() else "" comment = f" ({doc})" if doc and doc != kls.impl_getname() else ""
if "{{ identifier }}" in comment: if "{{ identifier }}" in comment and subconfig.identifiers:
comment = comment.replace("{{ identifier }}", str(subconfig.identifiers[-1])) comment = comment.replace("{{ identifier }}", str(subconfig.identifiers[-1]))
path = kls.impl_getpath() path = kls.impl_getpath()
if "{{ identifier }}" in path and subconfig.identifiers: if "{{ identifier }}" in path and subconfig.identifiers:

View file

@ -136,7 +136,9 @@ class Annotator(Walk):
) )
if family.version == "1.0" and "{{ suffix }}" in path: if family.version == "1.0" and "{{ suffix }}" in path:
path = path.replace("{{ suffix }}", "{{ identifier }}") path = path.replace("{{ suffix }}", "{{ identifier }}")
self.objectspace.dynamics_variable.setdefault(path, []).append(family.path) self.objectspace.dynamics_variable.setdefault(path, []).append(
family.path
)
self.objectspace.informations.add(family.path, "dynamic_variable", path) self.objectspace.informations.add(family.path, "dynamic_variable", path)
def change_modes(self): def change_modes(self):
@ -341,7 +343,7 @@ class Annotator(Walk):
if self._has_mode(variable): if self._has_mode(variable):
msg = _( msg = _(
'the variable "{0}" is in "{1}" mode but family has the higher family mode "{2}"' 'the variable "{0}" is in "{1}" mode but family has the higher family mode "{2}"'
).format(variable.name, variable_mode, family_mode) ).format(variable.path, variable_mode, family_mode)
raise DictConsistencyError(msg, 61, variable.xmlfiles) raise DictConsistencyError(msg, 61, variable.xmlfiles)
self._set_auto_mode(variable, family_mode) self._set_auto_mode(variable, family_mode)
if not variable.mode: if not variable.mode:

View file

@ -69,12 +69,28 @@ class Annotator(Walk): # pylint: disable=R0903
bool: "boolean", bool: "boolean",
float: "float", float: "float",
} }
self.verify_secret_managers()
self.verify_choices() self.verify_choices()
self.convert_variable() self.convert_variable()
self.convert_test() self.convert_test()
self.convert_examples() self.convert_examples()
self.convert_help() self.convert_help()
def verify_secret_managers(self):
for variable in self.get_variables():
if not variable.secret_manager:
continue
path = variable.path
if variable.type not in ["unix_user", "secret"]:
msg = _('only "unix_user" or "secret" variable type can have "secret_manager" attribute, but "{0}" has type "{1}"')
raise DictConsistencyError(msg.format(path, variable.type), 56, variable.xmlfiles)
if variable.multi and path not in self.objectspace.leaders:
msg = _('the variable "{0}" has attribute "secret_manager" but is a multi variable')
raise DictConsistencyError(msg.format(path), 57, variable.xmlfiles)
if variable.default is not None:
msg = _('the variable "{0}" has attribute "secret_manager" so must not have default value')
raise DictConsistencyError(msg.format(path), 59, variable.xmlfiles)
def convert_variable(self): def convert_variable(self):
"""convert variable""" """convert variable"""
for variable in self.get_variables(): for variable in self.get_variables():
@ -130,7 +146,9 @@ class Annotator(Walk): # pylint: disable=R0903
): ):
return return
# copy type and params # copy type and params
calculated_variable, identifier = variable.default.get_variable(self.objectspace) calculated_variable, identifier = variable.default.get_variable(
self.objectspace
)
if calculated_variable is None: if calculated_variable is None:
return return
variable.type = calculated_variable.type variable.type = calculated_variable.type

View file

@ -73,8 +73,7 @@ def get_level(module):
class _RougailConfig: class _RougailConfig:
def __init__(self, backward_compatibility: bool, def __init__(self, backward_compatibility: bool, add_extra_options: bool):
add_extra_options: bool):
self.backward_compatibility = backward_compatibility self.backward_compatibility = backward_compatibility
self.add_extra_options = add_extra_options self.add_extra_options = add_extra_options
self.root = None self.root = None
@ -86,7 +85,9 @@ class _RougailConfig:
) )
if self.root: if self.root:
rougailconfig.config.value.importation(self.config.value.exportation()) rougailconfig.config.value.importation(self.config.value.exportation())
rougailconfig.config.property.importation(self.config.property.exportation()) rougailconfig.config.property.importation(
self.config.property.exportation()
)
rougailconfig.config.property.read_only() rougailconfig.config.property.read_only()
rougailconfig.root = self.root rougailconfig.root = self.root
rougailconfig.config = self.config rougailconfig.config = self.config
@ -99,7 +100,9 @@ class _RougailConfig:
return rougailconfig return rougailconfig
def generate_config(self): def generate_config(self):
root, extra_vars = _rougail_config(self.backward_compatibility, self.add_extra_options) root, extra_vars = _rougail_config(
self.backward_compatibility, self.add_extra_options
)
self.root = root self.root = root
self.config = Config( self.config = Config(
self.root, self.root,
@ -343,6 +346,13 @@ load_unexist_redefine:
commandline: false commandline: false
default: False default: False
secret_manager:
pattern:
description: {_("The secret pattern to build item name in Bitwarden")}
help: {_("The pattern is in Jinja format")}
default: "{{{{ project }}}} - {{{{ environment }}}} - {{{{ service }}}} - {{{{ user }}}}"
""" """
processes = { processes = {
"structural": [], "structural": [],
@ -367,14 +377,22 @@ load_unexist_redefine:
{NAME}: {NAME}:
description: Select for {NAME} description: Select for {NAME}
choices:
""".format( """.format(
NAME=normalize_family(process), NAME=normalize_family(process),
) )
if process != "structural":
rougail_process += """
alternative_name: {NAME[0]}
""".format(
NAME=normalize_family(process),
)
rougail_process += """
choices:
"""
for obj in objects: for obj in objects:
rougail_process += f" - {obj['name']}\n" rougail_process += f" - {obj['name']}\n"
if process == "structural": if process == "structural":
# rougail_process += """ commandline: false # rougail_process += """ commandline: false
rougail_process += """ multi: true rougail_process += """ multi: true
default: default:
- directory - directory
@ -391,11 +409,11 @@ load_unexist_redefine:
jinja: | jinja: |
""" """
for hidden_output in hidden_outputs: for hidden_output in hidden_outputs:
rougail_process += """ {% if _.output == 'NAME' %} rougail_process += """ {% if _.output is not propertyerror and _.output == 'NAME' %}
Cannot load structural for NAME output Cannot load structural for NAME output
{% endif %}""".replace( {% endif %}
"NAME", hidden_output """.replace("NAME", hidden_output
) )
elif process == "user data": elif process == "user data":
rougail_process += """ multi: true rougail_process += """ multi: true
mandatory: false""" mandatory: false"""
@ -411,7 +429,7 @@ load_unexist_redefine:
jinja: | jinja: |
""" """
for hidden_output in hidden_outputs: for hidden_output in hidden_outputs:
rougail_process += """ {% if _.output == 'NAME' %} rougail_process += """ {% if _.output is not propertyerror and _.output == 'NAME' %}
Cannot load user data for NAME output Cannot load user data for NAME output
{% endif %}""".replace( {% endif %}""".replace(
"NAME", hidden_output "NAME", hidden_output
@ -455,10 +473,7 @@ default_params:
mandatory: false mandatory: false
default: {value} default: {value}
""" """
for process_empty in processes_empty:
rougail_process += process_empty
rougail_options += rougail_process rougail_options += rougail_process
# print(rougail_options)
convert = FakeRougailConvert(add_extra_options) convert = FakeRougailConvert(add_extra_options)
convert.init() convert.init()
convert.namespace = None convert.namespace = None
@ -468,6 +483,13 @@ default_params:
"1.1", "1.1",
YAML().load(rougail_options), YAML().load(rougail_options),
) )
for process_empty in processes_empty:
convert.parse_root_file(
"rougail.config",
"",
"1.1",
YAML().load(process_empty),
)
extra_vars = {} extra_vars = {}
objects = [] objects = []
for obj in sorted( for obj in sorted(

View file

@ -179,12 +179,16 @@ class ParserVariable:
] ]
self.structurals = rougailconfig["step.structural"] self.structurals = rougailconfig["step.structural"]
self.user_datas = rougailconfig["step.user_data"] self.user_datas = rougailconfig["step.user_data"]
self.output = rougailconfig["step.output"] try:
self.output = rougailconfig["step.output"]
except:
self.output = None
self.tiramisu_cache = rougailconfig["tiramisu_cache"] self.tiramisu_cache = rougailconfig["tiramisu_cache"]
self.load_unexist_redefine = rougailconfig["load_unexist_redefine"] self.load_unexist_redefine = rougailconfig["load_unexist_redefine"]
self.secret_pattern = rougailconfig['secret_manager.pattern']
# change default initkwargs in CONVERT_OPTION # change default initkwargs in CONVERT_OPTION
if hasattr(rougailconfig, 'config'): if hasattr(rougailconfig, "config"):
for sub_od in rougailconfig.config.option('default_params'): for sub_od in rougailconfig.config.option("default_params"):
for option in sub_od: for option in sub_od:
if option.owner.isdefault(): if option.owner.isdefault():
continue continue
@ -216,9 +220,7 @@ class ParserVariable:
family = type( family = type(
family.__name__ + "_" + structural, (family, module.Family), {} family.__name__ + "_" + structural, (family, module.Family), {}
) )
if "Walker" in module.__all__: if not self.walker and "Walker" in module.__all__:
if self.walker:
raise Exception("multiple walker defined!")
self.walker = module.Walker self.walker = module.Walker
self.variable = variable self.variable = variable
self.family = family self.family = family
@ -393,7 +395,7 @@ class ParserVariable:
exists = obj.pop("exists", None) exists = obj.pop("exists", None)
else: else:
exists = None exists = None
force_to_attrs = list(self.list_attributes(obj)) force_to_attrs = list(self.list_attributes(obj, filename))
for key, value in obj.items(): for key, value in obj.items():
if key in force_to_attrs: if key in force_to_attrs:
if key.startswith("_"): if key.startswith("_"):
@ -406,7 +408,9 @@ class ParserVariable:
if family_obj: if family_obj:
if exists in [None, True] and not obj.pop("redefine", False): if exists in [None, True] and not obj.pop("redefine", False):
msg = _('family "{0}" define multiple time').format(path) msg = _('family "{0}" define multiple time').format(path)
raise DictConsistencyError(msg, 32, self.paths[path].xmlfiles + [filename]) raise DictConsistencyError(
msg, 32, self.paths[path].xmlfiles + [filename]
)
# convert to Calculation objects # convert to Calculation objects
self.parse_parameters( self.parse_parameters(
path, path,
@ -499,10 +503,17 @@ class ParserVariable:
def list_attributes( def list_attributes(
self, self,
obj: Dict[str, Any], obj: Dict[str, Any],
filename: str,
) -> Iterator[str]: ) -> Iterator[str]:
"""List attributes""" """List attributes"""
force_to_variable = [] force_to_variable = []
for key, value in obj.items(): for key, value in obj.items():
if not isinstance(key, str):
raise DictConsistencyError(
f'a key is not in string format: {key}',
103,
[filename],
)
if key in force_to_variable: if key in force_to_variable:
continue continue
if key.startswith("_"): if key.startswith("_"):
@ -656,13 +667,17 @@ class ParserVariable:
version, version,
) )
self.parse_params(path, obj, filename) self.parse_params(path, obj, filename)
self.parse_secret_manager(path, obj, filename, version, family_is_dynamic)
exists = obj.pop("exists", None) exists = obj.pop("exists", None)
if path in self.paths: if path in self.paths:
if not self.load_unexist_redefine and exists is False: if not self.load_unexist_redefine:
return if exists is False:
if not obj.pop("redefine", False): return
msg = _('variable "{0}" define multiple time').format(path) if not obj.pop("redefine", False):
raise DictConsistencyError(msg, 45, self.paths[path].xmlfiles + [filename]) msg = _('variable "{0}" define multiple time').format(path)
raise DictConsistencyError(
msg, 45, self.paths[path].xmlfiles + [filename]
)
self.paths.add( self.paths.add(
path, path,
self.paths[path].model_copy(update=obj), self.paths[path].model_copy(update=obj),
@ -764,10 +779,11 @@ class ParserVariable:
if "params" not in obj: if "params" not in obj:
return return
if not isinstance(obj["params"], dict): if not isinstance(obj["params"], dict):
raise DictConsistencyError(_("params must be a dict for {0}").format(path), raise DictConsistencyError(
55, _("params must be a dict for {0}").format(path),
[filename], 55,
) [filename],
)
params = [] params = []
for key, val in obj["params"].items(): for key, val in obj["params"].items():
try: try:
@ -780,17 +796,45 @@ class ParserVariable:
is_follower=None, is_follower=None,
attribute=None, attribute=None,
family_is_dynamic=None, family_is_dynamic=None,
namespace=self.namespace,
xmlfiles=[filename], xmlfiles=[filename],
) )
) )
except ValidationError as err: except ValidationError as err:
raise DictConsistencyError( raise DictConsistencyError(
_('"{0}" has an invalid "params" for {1}: {2}').format(key, path, err), _('"{0}" has an invalid "params" for {1}: {2}').format(
key, path, err
),
54, 54,
[filename], [filename],
) from err ) from err
obj["params"] = params obj["params"] = params
def parse_secret_manager(self, path, obj, filename, version, family_is_dynamic):
"""Parse variable secret_manager"""
if "secret_manager" not in obj:
return
if not isinstance(obj["secret_manager"], dict):
raise DictConsistencyError(
_("secret_manager must be a dict for {0}").format(path),
64,
[filename],
)
secret_manager = {"type": "jinja",
"jinja": self.secret_pattern,
"params": obj["secret_manager"],
}
self.set_calculation(
obj,
"secret_manager",
secret_manager,
path,
family_is_dynamic,
False,
version,
[filename],
)
def add_variable( def add_variable(
self, self,
name: str, name: str,
@ -878,6 +922,9 @@ class ParserVariable:
calculations = calculations[1] calculations = calculations[1]
if not isinstance(value, dict) or attribute not in calculations: if not isinstance(value, dict) or attribute not in calculations:
return False return False
return self.check_auto_type(value)
def check_auto_type(self, value):
if "type" in value: if "type" in value:
return value["type"] in CALCULATION_TYPES return value["type"] in CALCULATION_TYPES
# auto set type # auto set type
@ -943,6 +990,7 @@ class ParserVariable:
val["family_is_dynamic"] = family_is_dynamic val["family_is_dynamic"] = family_is_dynamic
val["is_follower"] = is_follower val["is_follower"] = is_follower
val["attribute"] = attribute val["attribute"] = attribute
val["namespace"] = self.namespace
val["xmlfiles"] = xmlfiles val["xmlfiles"] = xmlfiles
if param_typ not in PARAM_TYPES: if param_typ not in PARAM_TYPES:
raise DictConsistencyError( raise DictConsistencyError(
@ -968,7 +1016,7 @@ class ParserVariable:
) )
# #
if typ == "identifier" and not family_is_dynamic: if typ == "identifier" and not family_is_dynamic:
msg = f'identifier calculation for "{attribute}" in "{path}" cannot be set none dynamic family' msg = f'identifier calculation for "{attribute}" in "{path}" cannot be set variable is not in dynamic family'
raise DictConsistencyError(msg, 53, xmlfiles) raise DictConsistencyError(msg, 53, xmlfiles)
if attribute in PROPERTY_ATTRIBUTE: if attribute in PROPERTY_ATTRIBUTE:
calc = CALCULATION_PROPERTY_TYPES[typ](**calculation_object) calc = CALCULATION_PROPERTY_TYPES[typ](**calculation_object)
@ -1106,7 +1154,9 @@ class RougailConvert(ParserVariable):
def parse_directories(self) -> None: def parse_directories(self) -> None:
if not self.walker: if not self.walker:
msg = _('invalid "structural" definition ({0}), we cannot load any structural file!').format(self.structurals) msg = _(
'invalid "structural" definition ({0}), we cannot load any structural file!'
).format(self.structurals)
raise DictConsistencyError(msg, 51, None) raise DictConsistencyError(msg, 51, None)
self.init() self.init()
self.walker(self) self.walker(self)

View file

@ -113,23 +113,23 @@ def get_convert_option_types():
if obj == tiramisu.SymLinkOption: if obj == tiramisu.SymLinkOption:
continue continue
if obj == tiramisu.ChoiceOption: if obj == tiramisu.ChoiceOption:
inst = obj('a', 'a', ('a',), **initkwargs) inst = obj("a", "a", ("a",), **initkwargs)
else: else:
inst = obj('a', 'a', **initkwargs) inst = obj("a", "a", **initkwargs)
extra = getattr(inst, '_extra', {}) extra = getattr(inst, "_extra", {})
if not extra: if not extra:
continue continue
params = [] params = []
for key, value in extra.items(): for key, value in extra.items():
if key.startswith('_'): if key.startswith("_"):
continue continue
multi = False multi = False
if isinstance(value, bool): if isinstance(value, bool):
key_type = 'boolean' key_type = "boolean"
elif isinstance(value, str): elif isinstance(value, str):
key_type = 'string' key_type = "string"
elif isinstance(value, list): elif isinstance(value, list):
key_type = 'string' key_type = "string"
multi = True multi = True
params.append((key, key_type, multi, value)) params.append((key, key_type, multi, value))
yield typ, params yield typ, params
@ -137,6 +137,7 @@ def get_convert_option_types():
class Param(BaseModel): class Param(BaseModel):
key: str key: str
namespace: Optional[str]
model_config = ConfigDict(extra="forbid") model_config = ConfigDict(extra="forbid")
def __init__( def __init__(
@ -191,19 +192,24 @@ class IndexParam(Param):
self, self,
**kwargs, **kwargs,
) -> None: ) -> None:
if not kwargs["is_follower"]: if not kwargs["is_follower"]:
msg = f'the variable "{kwargs["path"]}" is not a follower, so cannot have index type for param in "{kwargs["attribute"]}"' msg = f'the variable "{kwargs["path"]}" is not a follower, so cannot have index type for param in "{kwargs["attribute"]}"'
raise DictConsistencyError(msg, 25, kwargs["xmlfiles"]) raise DictConsistencyError(msg, 25, kwargs["xmlfiles"])
super().__init__(**kwargs) super().__init__(**kwargs)
class NamespaceParam(Param):
type: str
namespace: str
PARAM_TYPES = { PARAM_TYPES = {
"any": AnyParam, "any": AnyParam,
"variable": VariableParam, "variable": VariableParam,
"identifier": IdentifierParam, "identifier": IdentifierParam,
"information": InformationParam, "information": InformationParam,
"index": IndexParam, "index": IndexParam,
"namespace": NamespaceParam,
} }
@ -238,7 +244,7 @@ class Calculation(BaseModel):
) )
if not variable: if not variable:
if not param.get("optional"): if not param.get("optional"):
msg = f'cannot find variable "{param["variable"]}" defined attribute in "{self.attribute_name}" for "{self.path}"' msg = f'cannot find variable "{param["variable"]}" defined in attribute "{self.attribute_name}" for "{self.path}"'
raise DictConsistencyError(msg, 22, self.xmlfiles) raise DictConsistencyError(msg, 22, self.xmlfiles)
continue continue
if not isinstance(variable, objectspace.variable): if not isinstance(variable, objectspace.variable):
@ -288,6 +294,7 @@ class JinjaCalculation(Calculation):
"validators", "validators",
"choices", "choices",
"dynamic", "dynamic",
"secret_manager",
] ]
jinja: StrictStr jinja: StrictStr
params: Optional[List[Param]] = None params: Optional[List[Param]] = None
@ -366,7 +373,7 @@ class JinjaCalculation(Calculation):
self, self,
objectspace, objectspace,
) -> dict: ) -> dict:
if self.attribute_name == "default": if self.attribute_name in ["default", "secret_manager"]:
if self.return_type: if self.return_type:
raise Exception("return_type not allowed!") raise Exception("return_type not allowed!")
variable = objectspace.paths[self.path] variable = objectspace.paths[self.path]
@ -474,7 +481,8 @@ class _VariableCalculation(Calculation):
"variable": variable, "variable": variable,
"propertyerror": self.propertyerror, "propertyerror": self.propertyerror,
} }
if isinstance(self, VariableCalculation) and self.optional: is_variable_calculation = isinstance(self, VariableCalculation)
if is_variable_calculation and self.optional:
param["optional"] = self.optional param["optional"] = self.optional
if identifier: if identifier:
param["identifier"] = identifier param["identifier"] = identifier
@ -520,9 +528,10 @@ class _VariableCalculation(Calculation):
variable.multi variable.multi
or variable.path.rsplit(".", 1)[0] != self.path.rsplit(".", 1)[0] or variable.path.rsplit(".", 1)[0] != self.path.rsplit(".", 1)[0]
): ):
# it's not a follower or not in same leadership if is_variable_calculation:
msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", the variable "{variable.path}" is a multi' # it's not a follower or not in same leadership
raise DictConsistencyError(msg, 21, self.xmlfiles) msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", the variable "{variable.path}" is a multi'
raise DictConsistencyError(msg, 21, self.xmlfiles)
else: else:
params[None][0]["index"] = {"index": {"type": "index"}} params[None][0]["index"] = {"index": {"type": "index"}}
if self.path in objectspace.followers: if self.path in objectspace.followers:
@ -598,9 +607,12 @@ class VariablePropertyCalculation(_VariableCalculation):
when = self.when_not when = self.when_not
inverse = True inverse = True
else: else:
if variable.type != "boolean": if variable.multi:
raise Exception("only boolean!") when = []
when = True else:
if variable.type != "boolean":
raise Exception("only boolean!")
when = True
inverse = False inverse = False
params["when"] = when params["when"] = when
params["inverse"] = inverse params["inverse"] = inverse
@ -737,13 +749,31 @@ class IndexCalculation(Calculation):
} }
class NamespaceCalculation(Calculation):
attribute_name: Literal["default", "secret_manager"]
def to_function(
self,
objectspace,
) -> dict:
namespace = objectspace.namespace
if namespace:
namespace = objectspace.paths[namespace].description
return {
"function": "calc_value",
"params": {None: [namespace]},
}
CALCULATION_TYPES = { CALCULATION_TYPES = {
"jinja": JinjaCalculation, "jinja": JinjaCalculation,
"information": InformationCalculation, "information": InformationCalculation,
"variable": VariableCalculation, "variable": VariableCalculation,
"identifier": IdentifierCalculation, "identifier": IdentifierCalculation,
# FOR VERSION 1.0
"suffix": IdentifierCalculation, "suffix": IdentifierCalculation,
"index": IndexCalculation, "index": IndexCalculation,
"namespace": NamespaceCalculation,
} }
CALCULATION_PROPERTY_TYPES = { CALCULATION_PROPERTY_TYPES = {
"jinja": JinjaCalculation, "jinja": JinjaCalculation,
@ -753,6 +783,7 @@ CALCULATION_PROPERTY_TYPES = {
"index": IndexCalculation, "index": IndexCalculation,
} }
BASETYPE_CALC = Union[StrictBool, StrictInt, StrictFloat, StrictStr, Calculation, None] BASETYPE_CALC = Union[StrictBool, StrictInt, StrictFloat, StrictStr, Calculation, None]
SECRET_BASETYPE_CALC = Union[StrictStr, JinjaCalculation]
class Family(BaseModel): class Family(BaseModel):
@ -799,6 +830,7 @@ class Variable(BaseModel):
validators: Optional[List[Calculation]] = None validators: Optional[List[Calculation]] = None
# value # value
default: Union[List[BASETYPE_CALC], BASETYPE_CALC] = None default: Union[List[BASETYPE_CALC], BASETYPE_CALC] = None
secret_manager: Optional[JinjaCalculation] = None
# properties # properties
auto_save: bool = False auto_save: bool = False
mandatory: Union[None, bool, Calculation] = None mandatory: Union[None, bool, Calculation] = None

View file

@ -86,8 +86,10 @@ def test_propertyerror(value: Any) -> bool:
ENV.tests["propertyerror"] = test_propertyerror ENV.tests["propertyerror"] = test_propertyerror
def load_functions(path): def load_functions(path, dict_func=None):
global _SourceFileLoader, _spec_from_loader, _module_from_spec, func global _SourceFileLoader, _spec_from_loader, _module_from_spec, func
if dict_func is None:
dict_func = func
loader = _SourceFileLoader("func", path) loader = _SourceFileLoader("func", path)
spec = _spec_from_loader(loader.name, loader) spec = _spec_from_loader(loader.name, loader)
func_ = _module_from_spec(spec) func_ = _module_from_spec(spec)
@ -95,7 +97,7 @@ def load_functions(path):
for function in dir(func_): for function in dir(func_):
if function.startswith("_"): if function.startswith("_"):
continue continue
func[function] = getattr(func_, function) dict_func[function] = getattr(func_, function)
def rougail_calc_value(*args, __default_value=None, __internal_multi=False, **kwargs): def rougail_calc_value(*args, __default_value=None, __internal_multi=False, **kwargs):
@ -107,6 +109,21 @@ def rougail_calc_value(*args, __default_value=None, __internal_multi=False, **kw
return values return values
def kw_to_string(kw, root=None):
# {'_': {'interfaces': {'ip': ['192.168.44.19', '192.168.44.12'], 'domain_name': ['nginx-reverse-proxy.reverseproxy.test.net', 'nginx-reverse-proxy.localdns.test.net']}, 'dns_client_address': 'nginx-reverse-proxy.localdns.test.net'}}
for name, data in kw.items():
if root is None:
path = name
else:
path = root + '.' + name
if isinstance(data, dict):
yield from kw_to_string(data, root=path)
else:
yield f"{path}={data}"
pass
@function_waiting_for_error @function_waiting_for_error
def jinja_to_function( def jinja_to_function(
__internal_variable, __internal_variable,
@ -144,12 +161,13 @@ def jinja_to_function(
try: try:
values = ENV.get_template(__internal_jinja).render(kw, **func).strip() values = ENV.get_template(__internal_jinja).render(kw, **func).strip()
except Exception as err: except Exception as err:
kw_str = ", ".join(kw_to_string(kw))
raise ConfigError( raise ConfigError(
f'cannot calculating "{__internal_attribute}" attribute for variable "{__internal_variable}" in {display_xmlfiles(__internal_files)}: {err}' f'cannot calculating "{__internal_attribute}" attribute for variable "{__internal_variable}" in {display_xmlfiles(__internal_files)} with parameters "{kw_str}": {err}'
) from err ) from err
convert = CONVERT_OPTION[__internal_type].get("func", str) convert = CONVERT_OPTION[__internal_type].get("func", str)
if __internal_multi: if __internal_multi:
values = [convert(val) for val in values.split("\n") if val != ""] values = [convert(val.strip()) for val in values.split("\n") if val.strip() != ""]
if not values and __default_value is not None: if not values and __default_value is not None:
return __default_value return __default_value
return values return values
@ -204,6 +222,7 @@ func["jinja_to_property"] = jinja_to_property
func["jinja_to_property_help"] = jinja_to_property_help func["jinja_to_property_help"] = jinja_to_property_help
func["variable_to_property"] = variable_to_property func["variable_to_property"] = variable_to_property
func["valid_with_jinja"] = valid_with_jinja func["valid_with_jinja"] = valid_with_jinja
func["normalize_family"] = normalize_family
class ConvertDynOptionDescription(DynOptionDescription): class ConvertDynOptionDescription(DynOptionDescription):

View file

@ -350,6 +350,11 @@ class Common:
else: else:
value = str(param["value"]) value = str(param["value"])
return "ParamValue(" + value + ")" return "ParamValue(" + value + ")"
if param["type"] == "namespace":
namespace = param["namespace"]
if namespace:
namespace = self.objectspace.paths[param["namespace"]].description
return f"ParamValue('{namespace}')"
raise Exception("pfff") raise Exception("pfff")
def build_option_param( def build_option_param(

View file

@ -23,7 +23,12 @@ from typing import List
from re import findall from re import findall
from tiramisu import undefined, Calculation from tiramisu import undefined, Calculation
from tiramisu.error import PropertiesOptionError, LeadershipError, ConfigError, CancelParam from tiramisu.error import (
PropertiesOptionError,
LeadershipError,
ConfigError,
CancelParam,
)
from .i18n import _ from .i18n import _
from .object_model import CONVERT_OPTION from .object_model import CONVERT_OPTION
@ -151,8 +156,15 @@ class UserDatas:
if path not in self.values: if path not in self.values:
continue continue
options = self.values[path].get("options", {}) options = self.values[path].get("options", {})
if options.get('allow_secrets_variables', True) is False and option.type() == 'password': if (
self.errors.append(_('the variable "{0}" contains secrets and should not be defined in {1}').format(path, self.values[path]["source"])) options.get("allow_secrets_variables", True) is False
and option.type() == "password"
):
self.errors.append(
_(
'the variable "{0}" contains secrets and should not be defined in {1}'
).format(path, self.values[path]["source"])
)
continue continue
value = self.values[path]["values"] value = self.values[path]["values"]
needs_convert = options.get("needs_convert", False) needs_convert = options.get("needs_convert", False)
@ -212,7 +224,9 @@ class UserDatas:
option = self.config.option(path) option = self.config.option(path)
if option.isoptiondescription(): if option.isoptiondescription():
self.errors.warnings( self.errors.warnings(
_('the option "{0}" is an option description').format(option.path()) _('the option "{0}" is an option description').format(
option.path()
)
) )
continue continue
value = data["values"] value = data["values"]

View file

@ -0,0 +1,6 @@
{
"rougail.variable": {
"owner": "default",
"value": "Rougail"
}
}

View file

@ -0,0 +1,3 @@
{
"rougail.variable": "Rougail"
}

View file

@ -0,0 +1,6 @@
{
"rougail.variable": {
"owner": "default",
"value": "Rougail"
}
}

View file

@ -0,0 +1,15 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_2 = StrOption(name="variable", doc="a variable", default=Calculation(func['calc_value'], Params((ParamValue("Rougail")))), properties=frozenset({"standard"}), informations={'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2], properties=frozenset({"standard"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,6 @@
{
"rougail.variable": {
"owner": "default",
"value": "Rougail"
}
}

View file

@ -0,0 +1,3 @@
{
"rougail.variable": "Rougail"
}

View file

@ -0,0 +1,6 @@
{
"rougail.variable": {
"owner": "default",
"value": "Rougail"
}
}

View file

@ -0,0 +1,16 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
dict_env['default_rougail.variable'] = "{{ namespace }}"
option_2 = StrOption(name="variable", doc="a variable", default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_rougail.variable"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(False), '__internal_files': ParamValue(['../rougail-tests/structures/00_8calculation_param_namespace/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("rougail.variable"), 'namespace': ParamValue('Rougail')})), properties=frozenset({"standard"}), informations={'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2], properties=frozenset({"standard"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,6 @@
{
"rougail.condition": {
"owner": "default",
"value": []
}
}

View file

@ -0,0 +1,3 @@
{
"rougail.condition": []
}

View file

@ -0,0 +1,6 @@
{
"rougail.condition": {
"owner": "default",
"value": []
}
}

View file

@ -0,0 +1 @@
["rougail.condition"]

View file

@ -0,0 +1,16 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_2 = StrOption(name="condition", doc="a condition", multi=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
option_3 = StrOption(name="variable", doc="a variable", multi=True, properties=frozenset({"basic", "mandatory", Calculation(func['variable_to_property'], Params((ParamValue("disabled"), ParamOption(option_2)), kwargs={'__internal_multi': ParamValue(True), 'when': ParamValue([]), 'inverse': ParamValue(False)}), help_function=func['variable_to_property'])}), informations={'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3], properties=frozenset({"basic"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,11 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_1 = StrOption(name="condition", doc="a condition", multi=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
option_2 = StrOption(name="variable", doc="a variable", multi=True, properties=frozenset({"basic", "mandatory", Calculation(func['variable_to_property'], Params((ParamValue("disabled"), ParamOption(option_1)), kwargs={'__internal_multi': ParamValue(True), 'when': ParamValue([]), 'inverse': ParamValue(False)}), help_function=func['variable_to_property'])}), informations={'type': 'string'})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2])

View file

@ -0,0 +1,13 @@
{
"rougail.condition": {
"owner": "default",
"value": [
"val1",
"val2"
]
},
"rougail.variable": {
"owner": "default",
"value": []
}
}

View file

@ -0,0 +1,7 @@
{
"rougail.condition": [
"val1",
"val2"
],
"rougail.variable": []
}

View file

@ -0,0 +1,13 @@
{
"rougail.condition": {
"owner": "default",
"value": [
"val1",
"val2"
]
},
"rougail.variable": {
"owner": "default",
"value": []
}
}

View file

@ -0,0 +1 @@
["rougail.variable"]

View file

@ -0,0 +1,16 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_2 = StrOption(name="condition", doc="a condition", multi=True, default=["val1", "val2"], default_multi="val1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_3 = StrOption(name="variable", doc="a variable", multi=True, properties=frozenset({"basic", "mandatory", Calculation(func['variable_to_property'], Params((ParamValue("disabled"), ParamOption(option_2)), kwargs={'__internal_multi': ParamValue(True), 'when': ParamValue([]), 'inverse': ParamValue(False)}), help_function=func['variable_to_property'])}), informations={'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3], properties=frozenset({"basic"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,11 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_1 = StrOption(name="condition", doc="a condition", multi=True, default=["val1", "val2"], default_multi="val1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_2 = StrOption(name="variable", doc="a variable", multi=True, properties=frozenset({"basic", "mandatory", Calculation(func['variable_to_property'], Params((ParamValue("disabled"), ParamOption(option_1)), kwargs={'__internal_multi': ParamValue(True), 'when': ParamValue([]), 'inverse': ParamValue(False)}), help_function=func['variable_to_property'])}), informations={'type': 'string'})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2])

View file

@ -0,0 +1,25 @@
{
"rougail.var": {
"owner": "default",
"value": [
"val.1",
"val.2"
]
},
"rougail.dynval_1.var1": {
"owner": "default",
"value": "val.1"
},
"rougail.dynval_1.var2": {
"owner": "default",
"value": "val.1"
},
"rougail.dynval_2.var1": {
"owner": "default",
"value": "val.2"
},
"rougail.dynval_2.var2": {
"owner": "default",
"value": "val.2"
}
}

View file

@ -0,0 +1,10 @@
{
"rougail.var": [
"val.1",
"val.2"
],
"rougail.dynval_1.var1": "val.1",
"rougail.dynval_1.var2": "val.1",
"rougail.dynval_2.var1": "val.2",
"rougail.dynval_2.var2": "val.2"
}

View file

@ -0,0 +1,25 @@
{
"rougail.var": {
"owner": "default",
"value": [
"val.1",
"val.2"
]
},
"rougail.dynval_1.var1": {
"owner": "default",
"value": "val.1"
},
"rougail.dynval_1.var2": {
"owner": "default",
"value": "val.1"
},
"rougail.dynval_2.var1": {
"owner": "default",
"value": "val.2"
},
"rougail.dynval_2.var2": {
"owner": "default",
"value": "val.2"
}
}

View file

@ -0,0 +1,19 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
dict_env['default_rougail.dyn{{ identifier }}.var2'] = "{{ identifier }}"
option_2 = StrOption(name="var", doc="A suffix variable", multi=True, default=["val.1", "val.2"], default_multi="val.1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_4 = StrOption(name="var1", doc="A dynamic variable", default=Calculation(func['calc_value'], Params((ParamIdentifier()))), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_5 = StrOption(name="var2", doc="A dynamic variable", default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_rougail.dyn{{ identifier }}.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(False), '__internal_files': ParamValue(['../rougail-tests/structures/60_0family_dynamic_forbidden_char/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("rougail.dyn{{ identifier }}.var2"), 'identifier': ParamIdentifier()})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
optiondescription_3 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="A dynamic family", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_2)))), children=[option_4, option_5], properties=frozenset({"standard"}), informations={'dynamic_variable': 'rougail.var'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, optiondescription_3], properties=frozenset({"standard"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,14 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
dict_env['default_dyn{{ identifier }}.var2'] = "{{ identifier }}"
option_1 = StrOption(name="var", doc="A suffix variable", multi=True, default=["val.1", "val.2"], default_multi="val.1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_3 = StrOption(name="var1", doc="A dynamic variable", default=Calculation(func['calc_value'], Params((ParamIdentifier()))), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_4 = StrOption(name="var2", doc="A dynamic variable", default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_dyn{{ identifier }}.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(False), '__internal_files': ParamValue(['../rougail-tests/structures/60_0family_dynamic_forbidden_char/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("dyn{{ identifier }}.var2"), 'identifier': ParamIdentifier()})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
optiondescription_2 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="A dynamic family", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_1)))), children=[option_3, option_4], properties=frozenset({"standard"}), informations={'dynamic_variable': 'var'})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, optiondescription_2])

View file

@ -0,0 +1,10 @@
{
"rougail.var1": {
"owner": "default",
"value": []
},
"rougail.var2": {
"owner": "default",
"value": null
}
}

View file

@ -0,0 +1,4 @@
{
"rougail.var1": [],
"rougail.var2": null
}

View file

@ -0,0 +1,10 @@
{
"rougail.var1": {
"owner": "default",
"value": []
},
"rougail.var2": {
"owner": "default",
"value": null
}
}

View file

@ -0,0 +1 @@
["rougail.var2"]

View file

@ -0,0 +1,18 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_2 = StrOption(name="var1", doc="A suffix variable", multi=True, properties=frozenset({"standard"}), informations={'type': 'string', 'test': ('val1',)})
option_4 = StrOption(name="var", doc="A dynamic variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
optiondescription_3 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="dyn{{ identifier }}", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_2)))), children=[option_4], properties=frozenset({"basic"}), informations={'dynamic_variable': 'rougail.var1'})
option_5 = StrOption(name="var2", doc="A variable calculated", default=Calculation(func['calc_value'], Params((ParamDynOption(option_4, ["val1"], optional=True)))), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, optiondescription_3, option_5], properties=frozenset({"basic"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,10 @@
{
"rougail.var2": {
"owner": "default",
"value": null
},
"rougail.var1": {
"owner": "default",
"value": []
}
}

View file

@ -0,0 +1,4 @@
{
"rougail.var2": null,
"rougail.var1": []
}

View file

@ -0,0 +1,10 @@
{
"rougail.var2": {
"owner": "default",
"value": null
},
"rougail.var1": {
"owner": "default",
"value": []
}
}

View file

@ -0,0 +1 @@
["rougail.var2"]

View file

@ -0,0 +1,18 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_5 = StrOption(name="var", doc="A dynamic variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
option_2 = StrOption(name="var2", doc="A variable calculated", default=Calculation(func['calc_value'], Params((ParamDynOption(option_5, ["val1"], optional=True)))), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_3 = StrOption(name="var1", doc="A suffix variable", multi=True, properties=frozenset({"standard"}), informations={'type': 'string', 'test': ('val1', 'val2')})
optiondescription_4 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="dyn{{ identifier }}", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_3)))), children=[option_5], properties=frozenset({"basic"}), informations={'dynamic_variable': 'rougail.var1'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3, optiondescription_4], properties=frozenset({"basic"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,24 @@
{
"rougail.var": {
"owner": "default",
"value": [
"val1",
"val2"
]
},
"rougail.my_dyn_family_val1.var": {
"owner": "default",
"value": "val1"
},
"rougail.my_dyn_family_val2.var": {
"owner": "default",
"value": "val2"
},
"rougail.var2": {
"owner": "default",
"value": [
"val1",
"val2"
]
}
}

View file

@ -0,0 +1,12 @@
{
"rougail.var": [
"val1",
"val2"
],
"rougail.my_dyn_family_val1.var": "val1",
"rougail.my_dyn_family_val2.var": "val2",
"rougail.var2": [
"val1",
"val2"
]
}

View file

@ -0,0 +1,24 @@
{
"rougail.var": {
"owner": "default",
"value": [
"val1",
"val2"
]
},
"rougail.my_dyn_family_val1.var": {
"owner": "default",
"value": "val1"
},
"rougail.my_dyn_family_val2.var": {
"owner": "default",
"value": "val2"
},
"rougail.var2": {
"owner": "default",
"value": [
"val1",
"val2"
]
}
}

View file

@ -0,0 +1,18 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_2 = StrOption(name="var", doc="a suffix variable", multi=True, default=["val1", "val2"], default_multi="val1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_4 = StrOption(name="var", doc="a variable inside a dynamic family", default=Calculation(func['calc_value'], Params((ParamIdentifier()))), properties=frozenset({"standard"}), informations={'type': 'string'})
optiondescription_3 = ConvertDynOptionDescription(name="my_dyn_family_{{ identifier }}", doc="a dynamic family", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_2, notraisepropertyerror=True)), kwargs={'allow_none': ParamValue(True)})), children=[option_4], properties=frozenset({"standard"}), informations={'dynamic_variable': 'rougail.var'})
option_5 = StrOption(name="var2", doc="a variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_4)), kwargs={'__internal_multi': ParamValue(True)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, optiondescription_3, option_5], properties=frozenset({"standard"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -45,7 +45,7 @@ excludes = set([
]) ])
test_ok -= excludes test_ok -= excludes
test_raise -= excludes test_raise -= excludes
# test_ok = ['00_9default_calculation_multi_optional'] # test_ok = ['00_8calculation_param_namespace']
#test_ok = [] #test_ok = []
#test_raise = ['88valid_enum_invalid_default'] #test_raise = ['88valid_enum_invalid_default']
#test_raise = [] #test_raise = []