Compare commits
8 commits
477b0a2da1
...
d2a7c2b31c
| Author | SHA1 | Date | |
|---|---|---|---|
| d2a7c2b31c | |||
| 8b4ceddb81 | |||
| 4b41fd89fb | |||
| 7fad2b5d7d | |||
| a36d3cb9bf | |||
| add176331e | |||
| 245d5c02fb | |||
| ba0634f163 |
16 changed files with 269 additions and 42 deletions
|
|
@ -42,7 +42,6 @@ def calc_disk_usage(path, size='bytes'):
|
||||||
# do not calc if path is None
|
# do not calc if path is None
|
||||||
if path is None:
|
if path is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if size == 'bytes':
|
if size == 'bytes':
|
||||||
div = 1
|
div = 1
|
||||||
else:
|
else:
|
||||||
|
|
@ -60,12 +59,13 @@ usage = FloatOption('usage', 'Disk usage', Calculation(calc_disk_usage,
|
||||||
|
|
||||||
Finally add those options in option description and a Config:
|
Finally add those options in option description and a Config:
|
||||||
|
|
||||||
```
|
```python
|
||||||
disk = OptionDescription('disk', 'Verify disk usage', [filename, usage])
|
disk = OptionDescription('disk', 'Verify disk usage', [filename, usage])
|
||||||
root = OptionDescription('root', 'root', [disk])
|
root = OptionDescription('root', 'root', [disk])
|
||||||
async def main():
|
async def main():
|
||||||
config = await Config(root)
|
config = await Config(root)
|
||||||
await config.property.read_write()
|
await config.property.read_write()
|
||||||
|
return config
|
||||||
|
|
||||||
config = run(main())
|
config = run(main())
|
||||||
```
|
```
|
||||||
|
|
@ -109,26 +109,29 @@ returns:
|
||||||
|
|
||||||
When you enter a value it is validated:
|
When you enter a value it is validated:
|
||||||
|
|
||||||
>>> try:
|
```python
|
||||||
>>> config.option('disk.path').value.set('/unknown')
|
async def main():
|
||||||
>>> except ValueError as err:
|
try:
|
||||||
>>> print(err)
|
await config.option('disk.path').value.set('/unknown')
|
||||||
|
except ValueError as err:
|
||||||
|
print(err)
|
||||||
|
|
||||||
|
run(main())
|
||||||
|
```
|
||||||
|
|
||||||
|
returns:
|
||||||
|
|
||||||
|
```
|
||||||
"/unknown" is an invalid file name for "Path", this directory does not exist
|
"/unknown" is an invalid file name for "Path", this directory does not exist
|
||||||
|
```
|
||||||
We can also set a :doc:`calculation` as value. For example, we want to launch previous function but with in_gb to True as second argument:
|
|
||||||
|
|
||||||
>>> calc = Calculation(calc_disk_usage, Params((ParamOption(filename),
|
|
||||||
... ParamValue('gigabytes'))))
|
|
||||||
>>> config.option('disk.usage').value.set(calc)
|
|
||||||
>>> config.option('disk.usage').value.get()
|
|
||||||
622.6080360412598
|
|
||||||
|
|
||||||
#### Is value is valid?
|
#### Is value is valid?
|
||||||
|
|
||||||
To check is a value is valid:
|
To check is a value is valid:
|
||||||
|
|
||||||
>>> config.option('disk.path').value.valid()
|
```python
|
||||||
True
|
await config.option('disk.path').value.valid()
|
||||||
|
```
|
||||||
|
|
||||||
#### Display the default value
|
#### Display the default value
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -471,6 +471,22 @@ from tiramisu import FilenameOption
|
||||||
FilenameOption('file', 'file', '/etc/tiramisu/tiramisu.conf')
|
FilenameOption('file', 'file', '/etc/tiramisu/tiramisu.conf')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Unix file permissions: PermissionsOption
|
||||||
|
|
||||||
|
Valid the representing Unix permissions is an octal (base-8) notation.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from tiramisu import PermissionsOption
|
||||||
|
PermissionsOption('perms', 'perms', 755)
|
||||||
|
PermissionsOption('perms', 'perms', 1755)
|
||||||
|
```
|
||||||
|
|
||||||
|
This option doesn't allow (or display a warning with warnings_only):
|
||||||
|
|
||||||
|
- 777 (two weak value)
|
||||||
|
- others have more right than group
|
||||||
|
- group has more right than user
|
||||||
|
|
||||||
# Date option
|
# Date option
|
||||||
|
|
||||||
## Date option: DateOption
|
## Date option: DateOption
|
||||||
|
|
|
||||||
|
|
@ -436,6 +436,7 @@ async def test_config_od_type(config_type):
|
||||||
o2 = OptionDescription('val', '', [o])
|
o2 = OptionDescription('val', '', [o])
|
||||||
async with await Config(o2) as cfg:
|
async with await Config(o2) as cfg:
|
||||||
cfg = await get_config(cfg, config_type)
|
cfg = await get_config(cfg, config_type)
|
||||||
|
assert await cfg.option('val').option.type() == 'optiondescription'
|
||||||
assert await cfg.option('val.i').option.type() == 'integer'
|
assert await cfg.option('val.i').option.type() == 'integer'
|
||||||
assert not await list_sessions()
|
assert not await list_sessions()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -881,6 +881,51 @@ async def test_requires_dyndescription_in_dyn():
|
||||||
assert not await list_sessions()
|
assert not await list_sessions()
|
||||||
|
|
||||||
|
|
||||||
|
def calc_value_not_same(param, condition, expected, default, suffix):
|
||||||
|
if suffix == 'val1':
|
||||||
|
index = 0
|
||||||
|
else:
|
||||||
|
index = 1
|
||||||
|
return calc_value(param, condition=condition[index], expected=expected, default=default)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_requires_dyndescription_in_dyn_not_same():
|
||||||
|
boolean = BoolOption('boolean', '', True)
|
||||||
|
disabled_property = Calculation(calc_value_not_same,
|
||||||
|
Params(ParamValue('disabled'),
|
||||||
|
kwargs={'condition': ParamOption(boolean, raisepropertyerror=True),
|
||||||
|
'expected': ParamValue(False),
|
||||||
|
'default': ParamValue(None),
|
||||||
|
'suffix': ParamSuffix()}))
|
||||||
|
st = StrOption('st', '', properties=(disabled_property,))
|
||||||
|
dod1 = DynOptionDescription('dod1', '', [boolean], suffixes=Calculation(return_list))
|
||||||
|
dod2 = DynOptionDescription('dod2', '', [st], suffixes=Calculation(return_list))
|
||||||
|
od = OptionDescription('od', '', [dod1, dod2])
|
||||||
|
od2 = OptionDescription('od', '', [od])
|
||||||
|
async with await Config(od2) as cfg:
|
||||||
|
await cfg.property.read_write()
|
||||||
|
|
||||||
|
assert await cfg.option('od.dod2val1.stval1').value.get() is None
|
||||||
|
assert await cfg.option('od.dod2val2.stval2').value.get() is None
|
||||||
|
#
|
||||||
|
await cfg.option('od.dod1val1.booleanval1').value.set(False)
|
||||||
|
|
||||||
|
props = []
|
||||||
|
try:
|
||||||
|
await cfg.option('od.dod2val1.stval1').value.get()
|
||||||
|
except PropertiesOptionError as err:
|
||||||
|
props = err.proptype
|
||||||
|
assert props == frozenset(['disabled'])
|
||||||
|
props = []
|
||||||
|
await cfg.option('od.dod2val2.stval2').value.get()
|
||||||
|
#
|
||||||
|
await cfg.option('od.dod1val1.booleanval1').value.set(True)
|
||||||
|
assert await cfg.option('od.dod2val1.stval1').value.get() is None
|
||||||
|
assert await cfg.option('od.dod2val2.stval2').value.get() is None
|
||||||
|
assert not await list_sessions()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_requires_dyndescription2():
|
async def test_requires_dyndescription2():
|
||||||
boolean = BoolOption('boolean', '', True)
|
boolean = BoolOption('boolean', '', True)
|
||||||
|
|
|
||||||
|
|
@ -1074,3 +1074,16 @@ async def test_follower_properties():
|
||||||
await cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.get() == ('aproperty', 'newproperty', 'newproperty1')
|
await cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.get() == ('aproperty', 'newproperty', 'newproperty1')
|
||||||
await cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == ('aproperty', 'newproperty1')
|
await cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == ('aproperty', 'newproperty1')
|
||||||
assert not await list_sessions()
|
assert not await list_sessions()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_get_leader(config_type):
|
||||||
|
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
|
||||||
|
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
|
||||||
|
interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
|
||||||
|
maconfig = OptionDescription('conf', '', [interface1])
|
||||||
|
async with await Config(maconfig) as cfg:
|
||||||
|
option = await cfg.option('ip_admin_eth0.netmask_admin_eth0').option.leader()
|
||||||
|
assert await option.option.get() == ip_admin_eth0
|
||||||
|
|
||||||
|
assert not await list_sessions()
|
||||||
|
|
|
||||||
|
|
@ -1514,6 +1514,18 @@ async def test_calc_value_remove_duplicate(config_type):
|
||||||
assert not await list_sessions()
|
assert not await list_sessions()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_calc_value_remove_duplicate2(config_type):
|
||||||
|
val1 = StrOption('val1', "", ['val1', 'val1'], multi=True, properties=('notunique',))
|
||||||
|
val2 = StrOption('val2', "", ['val1', 'val1'], multi=True, properties=('notunique',))
|
||||||
|
val3 = StrOption('val3', "", Calculation(calc_value, Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), remove_duplicate_value=ParamValue(True), join=ParamValue('-'))), multi=True)
|
||||||
|
od = OptionDescription('root', '', [val1, val2, val3])
|
||||||
|
async with await Config(od) as cfg:
|
||||||
|
cfg = await get_config(cfg, config_type)
|
||||||
|
assert await cfg.value.dict() == {'val1': ['val1', 'val1'], 'val2': ['val1', 'val1'], 'val3': ['val1-val1']}
|
||||||
|
assert not await list_sessions()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_calc_value_join(config_type):
|
async def test_calc_value_join(config_type):
|
||||||
val1 = StrOption('val1', "", 'val1')
|
val1 = StrOption('val1', "", 'val1')
|
||||||
|
|
|
||||||
36
tests/test_option_permissions.py
Normal file
36
tests/test_option_permissions.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
"configuration objects global API"
|
||||||
|
from .autopath import do_autopath
|
||||||
|
do_autopath()
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tiramisu import PermissionsOption
|
||||||
|
|
||||||
|
|
||||||
|
def test_permissions():
|
||||||
|
PermissionsOption('a', '', 640)
|
||||||
|
PermissionsOption('a', '', 642)
|
||||||
|
PermissionsOption('a', '', 751)
|
||||||
|
PermissionsOption('a', '', 753)
|
||||||
|
PermissionsOption('a', '', 7555)
|
||||||
|
PermissionsOption('a', '', 1755)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
PermissionsOption('a', '', 800)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
PermissionsOption('a', '', 75)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
PermissionsOption('a', '', 77775)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
PermissionsOption('a', '', '755')
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
PermissionsOption('a', '', 'string')
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
PermissionsOption('a', '', 800)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
PermissionsOption('a', '', 1575)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
PermissionsOption('a', '', 1557)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
PermissionsOption('a', '', 777)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
PermissionsOption('a', '', 1777)
|
||||||
|
|
@ -407,6 +407,8 @@ class TiramisuOptionOption(_TiramisuOptionOptionDescription):
|
||||||
|
|
||||||
@option_and_connection
|
@option_and_connection
|
||||||
async def type(self):
|
async def type(self):
|
||||||
|
if self._option_bag.option.impl_is_optiondescription():
|
||||||
|
return 'optiondescription'
|
||||||
return self._option_bag.option.get_type()
|
return self._option_bag.option.get_type()
|
||||||
|
|
||||||
@option_and_connection
|
@option_and_connection
|
||||||
|
|
@ -437,6 +439,12 @@ class TiramisuOptionOption(_TiramisuOptionOptionDescription):
|
||||||
self._option_bag.config_bag,
|
self._option_bag.config_bag,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@option_and_connection
|
||||||
|
async def leader(self):
|
||||||
|
return TiramisuOption(self._option_bag.option.impl_get_leadership().get_leader().impl_getpath(),
|
||||||
|
None,
|
||||||
|
self._option_bag.config_bag)
|
||||||
|
|
||||||
|
|
||||||
class TiramisuOptionOwner(CommonTiramisuOption):
|
class TiramisuOptionOwner(CommonTiramisuOption):
|
||||||
#FIXME optiondescription must not have Owner!
|
#FIXME optiondescription must not have Owner!
|
||||||
|
|
@ -699,7 +707,8 @@ class TiramisuOptionValue(CommonTiramisuOption):
|
||||||
flatten=False,
|
flatten=False,
|
||||||
withwarning: bool=False,
|
withwarning: bool=False,
|
||||||
fullpath=False,
|
fullpath=False,
|
||||||
leader_to_list=False):
|
leader_to_list=False,
|
||||||
|
):
|
||||||
"""Dict with path as key and value"""
|
"""Dict with path as key and value"""
|
||||||
name = self._option_bag.option.impl_getname()
|
name = self._option_bag.option.impl_getname()
|
||||||
subconfig = await self._subconfig.get_subconfig(self._option_bag)
|
subconfig = await self._subconfig.get_subconfig(self._option_bag)
|
||||||
|
|
@ -768,6 +777,7 @@ class TiramisuOptionValue(CommonTiramisuOption):
|
||||||
option = self._option_bag.option
|
option = self._option_bag.option
|
||||||
values = self._option_bag.config_bag.context.cfgimpl_get_values()
|
values = self._option_bag.config_bag.context.cfgimpl_get_values()
|
||||||
if option.impl_is_follower() and self._option_bag.index is None:
|
if option.impl_is_follower() and self._option_bag.index is None:
|
||||||
|
# IF OU PAS IF ?? if self._option_bag.option.impl_is_symlinkoption():
|
||||||
value = []
|
value = []
|
||||||
length = await self._subconfig.cfgimpl_get_length_leadership(self._option_bag)
|
length = await self._subconfig.cfgimpl_get_length_leadership(self._option_bag)
|
||||||
settings = self._option_bag.config_bag.context.cfgimpl_get_settings()
|
settings = self._option_bag.config_bag.context.cfgimpl_get_settings()
|
||||||
|
|
@ -779,6 +789,7 @@ class TiramisuOptionValue(CommonTiramisuOption):
|
||||||
soption_bag.properties = await settings.getproperties(soption_bag)
|
soption_bag.properties = await settings.getproperties(soption_bag)
|
||||||
value.append(await values.getdefaultvalue(soption_bag))
|
value.append(await values.getdefaultvalue(soption_bag))
|
||||||
return value
|
return value
|
||||||
|
# raise APIError('index must be set with a follower option')
|
||||||
else:
|
else:
|
||||||
return await values.getdefaultvalue(self._option_bag)
|
return await values.getdefaultvalue(self._option_bag)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ class ParamDynOption(ParamOption):
|
||||||
dynoptiondescription: 'DynOptionDescription',
|
dynoptiondescription: 'DynOptionDescription',
|
||||||
notraisepropertyerror: bool=False,
|
notraisepropertyerror: bool=False,
|
||||||
raisepropertyerror: bool=False,
|
raisepropertyerror: bool=False,
|
||||||
|
optional: bool=False,
|
||||||
todict: bool=False,
|
todict: bool=False,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(option,
|
super().__init__(option,
|
||||||
|
|
@ -99,6 +100,7 @@ class ParamDynOption(ParamOption):
|
||||||
)
|
)
|
||||||
self.suffix = suffix
|
self.suffix = suffix
|
||||||
self.dynoptiondescription = dynoptiondescription
|
self.dynoptiondescription = dynoptiondescription
|
||||||
|
self.optional = optional
|
||||||
|
|
||||||
|
|
||||||
class ParamSelfOption(Param):
|
class ParamSelfOption(Param):
|
||||||
|
|
@ -273,13 +275,19 @@ async def manager_callback(callbk: Param,
|
||||||
except PropertiesOptionError as err:
|
except PropertiesOptionError as err:
|
||||||
# raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation
|
# raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation
|
||||||
if callbk.notraisepropertyerror or callbk.raisepropertyerror:
|
if callbk.notraisepropertyerror or callbk.raisepropertyerror:
|
||||||
raise err
|
raise err from err
|
||||||
raise ConfigError(_('unable to carry out a calculation for "{}"'
|
raise ConfigError(_('unable to carry out a calculation for "{}"'
|
||||||
', {}').format(option.impl_get_display_name(), err), err)
|
', {}').format(option.impl_get_display_name(), err), err) from err
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
raise ValueError(_('the option "{0}" is used in a calculation but is invalid ({1})').format(option_bag.option.impl_get_display_name(), err))
|
raise ValueError(_('the option "{0}" is used in a calculation but is invalid ({1})').format(option_bag.option.impl_get_display_name(), err)) from err
|
||||||
except AttributeError as err:
|
except AttributeError as err:
|
||||||
raise ConfigError(_(f'unable to get value for calculating "{option_bag.option.impl_get_display_name()}", {err}'))
|
if isinstance(callbk, ParamDynOption) and callbk.optional:
|
||||||
|
# cannot acces, simulate a propertyerror
|
||||||
|
raise PropertiesOptionError(option_bag,
|
||||||
|
['configerror'],
|
||||||
|
config_bag.context.cfgimpl_get_settings(),
|
||||||
|
)
|
||||||
|
raise ConfigError(_(f'unable to get value for calculating "{option_bag.option.impl_get_display_name()}", {err}')) from err
|
||||||
return value
|
return value
|
||||||
|
|
||||||
async def get_option_bag(config_bag,
|
async def get_option_bag(config_bag,
|
||||||
|
|
@ -351,6 +359,7 @@ async def manager_callback(callbk: Param,
|
||||||
callbk_option = callbk.option
|
callbk_option = callbk.option
|
||||||
callbk_options = None
|
callbk_options = None
|
||||||
if callbk_option.issubdyn():
|
if callbk_option.issubdyn():
|
||||||
|
found = False
|
||||||
if isinstance(callbk, ParamDynOption):
|
if isinstance(callbk, ParamDynOption):
|
||||||
subdyn = callbk.dynoptiondescription
|
subdyn = callbk.dynoptiondescription
|
||||||
rootpath = subdyn.impl_getpath() + callbk.suffix
|
rootpath = subdyn.impl_getpath() + callbk.suffix
|
||||||
|
|
@ -358,7 +367,22 @@ async def manager_callback(callbk: Param,
|
||||||
callbk_option = callbk_option.to_dynoption(rootpath,
|
callbk_option = callbk_option.to_dynoption(rootpath,
|
||||||
suffix,
|
suffix,
|
||||||
subdyn)
|
subdyn)
|
||||||
elif not option.impl_is_dynsymlinkoption():
|
found = True
|
||||||
|
elif option.impl_is_dynsymlinkoption():
|
||||||
|
rootpath = option.rootpath
|
||||||
|
call_path = callbk_option.impl_getpath()
|
||||||
|
if call_path.startswith(option.opt.impl_getpath().rsplit('.', 1)[0]):
|
||||||
|
# in same dynoption
|
||||||
|
if len(callbk_option.impl_getpath().split('.')) == len(rootpath.split('.')):
|
||||||
|
rootpath = rootpath.rsplit('.', 1)[0]
|
||||||
|
suffix = option.impl_getsuffix()
|
||||||
|
subdyn = callbk_option.getsubdyn()
|
||||||
|
callbk_option = callbk_option.to_dynoption(rootpath,
|
||||||
|
suffix,
|
||||||
|
subdyn,
|
||||||
|
)
|
||||||
|
found = True
|
||||||
|
if not found:
|
||||||
callbk_options = []
|
callbk_options = []
|
||||||
dynopt = callbk_option.getsubdyn()
|
dynopt = callbk_option.getsubdyn()
|
||||||
rootpath = dynopt.impl_getpath()
|
rootpath = dynopt.impl_getpath()
|
||||||
|
|
@ -370,16 +394,6 @@ async def manager_callback(callbk: Param,
|
||||||
suffix,
|
suffix,
|
||||||
dynopt)
|
dynopt)
|
||||||
callbk_options.append(doption)
|
callbk_options.append(doption)
|
||||||
else:
|
|
||||||
#FIXME in same dynamic option?
|
|
||||||
rootpath = option.rootpath
|
|
||||||
if len(callbk_option.impl_getpath().split('.')) == len(rootpath.split('.')):
|
|
||||||
rootpath = rootpath.rsplit('.', 1)[0]
|
|
||||||
suffix = option.impl_getsuffix()
|
|
||||||
subdyn = callbk_option.getsubdyn()
|
|
||||||
callbk_option = callbk_option.to_dynoption(rootpath,
|
|
||||||
suffix,
|
|
||||||
subdyn)
|
|
||||||
if leadership_must_have_index and callbk_option.impl_is_follower() and index is None:
|
if leadership_must_have_index and callbk_option.impl_is_follower() and index is None:
|
||||||
raise Break()
|
raise Break()
|
||||||
if config_bag is undefined:
|
if config_bag is undefined:
|
||||||
|
|
|
||||||
|
|
@ -361,7 +361,7 @@ class CalcValue:
|
||||||
value = []
|
value = []
|
||||||
elif None in value and not allow_none:
|
elif None in value and not allow_none:
|
||||||
value = []
|
value = []
|
||||||
elif remove_duplicate_value:
|
if remove_duplicate_value:
|
||||||
new_value = []
|
new_value = []
|
||||||
for val in value:
|
for val in value:
|
||||||
if val not in new_value:
|
if val not in new_value:
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ from .dateoption import DateOption
|
||||||
from .filenameoption import FilenameOption
|
from .filenameoption import FilenameOption
|
||||||
from .passwordoption import PasswordOption
|
from .passwordoption import PasswordOption
|
||||||
from .macoption import MACOption
|
from .macoption import MACOption
|
||||||
|
from .permissionsoption import PermissionsOption
|
||||||
|
|
||||||
|
|
||||||
__all__ = ('Leadership', 'OptionDescription', 'DynOptionDescription',
|
__all__ = ('Leadership', 'OptionDescription', 'DynOptionDescription',
|
||||||
|
|
@ -33,4 +34,4 @@ __all__ = ('Leadership', 'OptionDescription', 'DynOptionDescription',
|
||||||
'IPOption', 'PortOption', 'NetworkOption', 'NetmaskOption',
|
'IPOption', 'PortOption', 'NetworkOption', 'NetmaskOption',
|
||||||
'BroadcastOption', 'DomainnameOption', 'EmailOption', 'URLOption',
|
'BroadcastOption', 'DomainnameOption', 'EmailOption', 'URLOption',
|
||||||
'UsernameOption', 'GroupnameOption', 'FilenameOption', 'PasswordOption', 'submulti',
|
'UsernameOption', 'GroupnameOption', 'FilenameOption', 'PasswordOption', 'submulti',
|
||||||
'RegexpOption', 'MACOption')
|
'RegexpOption', 'MACOption', 'PermissionsOption')
|
||||||
|
|
|
||||||
|
|
@ -74,12 +74,10 @@ class DomainnameOption(StrOption):
|
||||||
else:
|
else:
|
||||||
min_time = 1
|
min_time = 1
|
||||||
regexp = r'((?!-)[a-z0-9-]{{{1},{0}}}\.){{{1},}}[a-z0-9-]{{1,{0}}}'.format(self._get_len(type), min_time)
|
regexp = r'((?!-)[a-z0-9-]{{{1},{0}}}\.){{{1},}}[a-z0-9-]{{1,{0}}}'.format(self._get_len(type), min_time)
|
||||||
msg = _('only lowercase, number, "-" and "." characters are allowed')
|
|
||||||
msg_warning = _('only lowercase, number, "-" and "." characters are recommanded')
|
|
||||||
else:
|
else:
|
||||||
regexp = r'((?!-)[a-z0-9-]{{1,{0}}})'.format(self._get_len(type))
|
regexp = r'((?!-)[a-z0-9-]{{1,{0}}})'.format(self._get_len(type))
|
||||||
msg = _('only lowercase, number and "-" characters are allowed')
|
msg = _('must start with lowercase characters followed by lowercase characters, number, "-" and "." characters are allowed')
|
||||||
msg_warning = _('only lowercase, number and "-" characters are recommanded')
|
msg_warning = _('must start with lowercase characters followed by lowercase characters, number, "-" and "." characters are recommanded')
|
||||||
if allow_ip:
|
if allow_ip:
|
||||||
msg = _('could be a IP, otherwise {}').format(msg)
|
msg = _('could be a IP, otherwise {}').format(msg)
|
||||||
msg_warning = _('could be a IP, otherwise {}').format(msg_warning)
|
msg_warning = _('could be a IP, otherwise {}').format(msg_warning)
|
||||||
|
|
|
||||||
75
tiramisu/option/permissionsoption.py
Normal file
75
tiramisu/option/permissionsoption.py
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (C) 2023 Team tiramisu (see AUTHORS for all contributors)
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published by the
|
||||||
|
# Free Software Foundation, either version 3 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 Lesser General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# The original `Config` design model is unproudly borrowed from
|
||||||
|
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
|
||||||
|
# the whole pypy projet is under MIT licence
|
||||||
|
# ____________________________________________________________
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ..setting import undefined, Undefined, OptionBag
|
||||||
|
from ..i18n import _
|
||||||
|
from .option import Option
|
||||||
|
from .intoption import IntOption
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionsOption(IntOption):
|
||||||
|
"""Unix file permissions
|
||||||
|
Valid the representing Unix permissions is an octal (base-8) notation.
|
||||||
|
This notation consists of at least three digits (owner, group, and others).
|
||||||
|
If a fourth digit is present to the setuid bit, the setgid bit and the sticky bit attributes.
|
||||||
|
This option is an integer value.
|
||||||
|
"""
|
||||||
|
__slots__ = tuple()
|
||||||
|
perm_re = re.compile(r"^[0-7]{3,4}$")
|
||||||
|
_type = 'permissions'
|
||||||
|
_display_name = _('unix file permissions')
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
#do not display intoption attributs
|
||||||
|
super().__init__(*args,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def validate(self,
|
||||||
|
value: str) -> None:
|
||||||
|
super().validate(value)
|
||||||
|
if not self.perm_re.search(str(value)):
|
||||||
|
raise ValueError(_('only 3 or 4 octal digits are allowed'))
|
||||||
|
|
||||||
|
def second_level_validation(self,
|
||||||
|
value: str,
|
||||||
|
warnings_only: bool) -> None:
|
||||||
|
old_digit = 7
|
||||||
|
str_value = str(value)
|
||||||
|
if len(str_value) == 4:
|
||||||
|
str_value = str_value[1:]
|
||||||
|
for idx, digit in enumerate(str_value):
|
||||||
|
new_digit = int(digit)
|
||||||
|
if old_digit < new_digit:
|
||||||
|
if idx == 1:
|
||||||
|
old = _('user')
|
||||||
|
new = _('group')
|
||||||
|
else:
|
||||||
|
old = _('group')
|
||||||
|
new = _('other')
|
||||||
|
raise ValueError(_(f'{new} has more right than {old}'))
|
||||||
|
old_digit = new_digit
|
||||||
|
if str_value == '777':
|
||||||
|
raise ValueError(_(f'too weak'))
|
||||||
|
|
@ -19,11 +19,8 @@
|
||||||
# the whole pypy projet is under MIT licence
|
# the whole pypy projet is under MIT licence
|
||||||
# ____________________________________________________________
|
# ____________________________________________________________
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
|
|
||||||
from ..setting import undefined, Undefined, OptionBag
|
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from .option import Option
|
|
||||||
from .stroption import StrOption
|
from .stroption import StrOption
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ FORBIDDEN_SET_PERMISSIVES = frozenset(['force_default_on_freeze',
|
||||||
'force_metaconfig_on_freeze',
|
'force_metaconfig_on_freeze',
|
||||||
'force_store_value'])
|
'force_store_value'])
|
||||||
ALLOWED_LEADER_PROPERTIES = frozenset(['empty',
|
ALLOWED_LEADER_PROPERTIES = frozenset(['empty',
|
||||||
|
'notempty',
|
||||||
'notunique',
|
'notunique',
|
||||||
'unique',
|
'unique',
|
||||||
'force_store_value',
|
'force_store_value',
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,11 @@ class Values:
|
||||||
value,
|
value,
|
||||||
reset_cache=True):
|
reset_cache=True):
|
||||||
if isinstance(value, Calculation):
|
if isinstance(value, Calculation):
|
||||||
value = await value.execute(option_bag)
|
try:
|
||||||
|
value = await value.execute(option_bag)
|
||||||
|
except ConfigError as err:
|
||||||
|
msg = _(f'error when calculating "{option_bag.option.impl_get_display_name()}": {err} : {option_bag.path}')
|
||||||
|
raise ConfigError(msg) from err
|
||||||
elif isinstance(value, (list, tuple)):
|
elif isinstance(value, (list, tuple)):
|
||||||
value = await self._do_value_list(value, option_bag)
|
value = await self._do_value_list(value, option_bag)
|
||||||
if reset_cache:
|
if reset_cache:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue