diff --git a/libioc/Config/Jail/BaseConfig.py b/libioc/Config/Jail/BaseConfig.py index 36c1c04f..fee4d737 100644 --- a/libioc/Config/Jail/BaseConfig.py +++ b/libioc/Config/Jail/BaseConfig.py @@ -29,6 +29,7 @@ import libioc.Config.Data import libioc.Config.Jail.Globals import libioc.Config.Jail.Properties +import libioc.Config.Jail.Defaults import libioc.errors import libioc.helpers import libioc.helpers_object @@ -736,7 +737,13 @@ def set( # noqa: T484 existed_before = (key in self.keys()) is True try: - hash_before = str(BaseConfig.__getitem__(self, key)).__hash__() + if isinstance( + self, + libioc.Config.Jail.Defaults.JailConfigDefaults + ) is True: + hash_before = str(self.__getitem__(key)).__hash__() + else: + hash_before = str(BaseConfig.__getitem__(self, key)).__hash__() except Exception: if existed_before is True: raise diff --git a/libioc/Config/Prototype.py b/libioc/Config/Prototype.py index 81dd2b92..414b0f42 100644 --- a/libioc/Config/Prototype.py +++ b/libioc/Config/Prototype.py @@ -27,6 +27,7 @@ import os.path import libioc.helpers_object +import libioc.Config.Data # MyPy import libioc.Logger @@ -44,7 +45,7 @@ class Prototype: """Prototype of a JailConfig.""" logger: typing.Type['libioc.Logger.Logger'] - data: ConfigDataDict = {} + data: ConfigDataDict _file: str def __init__( @@ -54,6 +55,7 @@ def __init__( ) -> None: self.logger = libioc.helpers_object.init_logger(self, logger) + self.data = libioc.Config.Data.Data() if file is not None: self._file = file @@ -67,7 +69,7 @@ def file(self) -> str: def file(self, value: str) -> None: self._file = value - def read(self) -> ConfigDataDict: + def read(self) -> libioc.Config.Data.Data: """ Read from the configuration file. @@ -93,14 +95,14 @@ def write(self, data: ConfigDataDict) -> None: def map_input( self, data: typing.Union[typing.TextIO, ConfigDataDict] - ) -> ConfigDataDict: + ) -> libioc.Config.Data.Data: """ Map input data (for reading from the configuration). Implementing classes may provide individual mappings. """ if not isinstance(data, typing.TextIO): - return data + return libioc.Config.Data.Data(data) raise NotImplementedError("Mapping not implemented on the prototype") diff --git a/libioc/Config/Type/JSON.py b/libioc/Config/Type/JSON.py index e2d1750e..0994f6bd 100644 --- a/libioc/Config/Type/JSON.py +++ b/libioc/Config/Type/JSON.py @@ -38,13 +38,22 @@ class ConfigJSON(libioc.Config.Prototype.Prototype): config_type = "json" - def map_input(self, data: typing.TextIO) -> typing.Dict[str, typing.Any]: + def map_input(self, data: typing.TextIO) -> libioc.Config.Data.Data: """Parse and normalize JSON data.""" - if data == "": - return {} try: - result = json.load(data) # type: typing.Dict[str, typing.Any] - return result + content = data.read().strip() + except (FileNotFoundError, PermissionError) as e: + raise libioc.errors.JailConfigError( + message=str(e), + logger=self.logger + ) + + if content == "": + return libioc.Config.Data.Data() + + try: + result = json.loads(content) # type: typing.Dict[str, typing.Any] + return libioc.Config.Data.Data(result) except json.decoder.JSONDecodeError as e: raise libioc.errors.JailConfigError( message=str(e), diff --git a/libioc/Config/Type/ZFS.py b/libioc/Config/Type/ZFS.py index be8f7b5f..34054e23 100644 --- a/libioc/Config/Type/ZFS.py +++ b/libioc/Config/Type/ZFS.py @@ -95,10 +95,12 @@ def write(self, data: dict) -> None: def map_input( self, data: typing.Dict[str, str] - ) -> libioc.Config.Prototype.ConfigDataDict: + ) -> libioc.Config.Data.Data: """Normalize data read from ZFS properties.""" parse_user_input = libioc.helpers.parse_user_input - return dict([(x, parse_user_input(y)) for (x, y) in data.items()]) + return libioc.Config.Data.Data( + dict([(x, parse_user_input(y)) for (x, y) in data.items()]) + ) def _to_string( self, diff --git a/libioc/Resource.py b/libioc/Resource.py index 3f993993..9f070dbc 100644 --- a/libioc/Resource.py +++ b/libioc/Resource.py @@ -323,7 +323,7 @@ def abspath(self, relative_path: str) -> str: """Return the absolute path of a path relative to the resource.""" return str(os.path.join(self.dataset.mountpoint, relative_path)) - def _write_config(self, data: dict) -> None: + def _write_config(self, data: libioc.Config.Data.Data) -> None: """Write the configuration to disk.""" self.config_handler.write(data) @@ -437,4 +437,4 @@ def destroy( def save(self) -> None: """Save changes to the default configuration.""" - self._write_config(self.config.user_data.nested) + self._write_config(self.config.user_data) diff --git a/tests/test_Config.py b/tests/test_Config.py index 4ab5f3c8..e2819877 100644 --- a/tests/test_Config.py +++ b/tests/test_Config.py @@ -165,11 +165,14 @@ def default_resource( root_dataset: libzfs.ZFSDataset, zfs: libzfs.ZFS, ) -> 'libioc.Resource.DefaultResource': - return libioc.Resource.DefaultResource( + default_resource = libioc.Resource.DefaultResource( dataset=root_dataset, logger=logger, zfs=zfs ) + yield default_resource + if os.path.isfile(default_resource.config_handler.file): + os.remove(default_resource.config_handler.file) def test_default_config_path( self, @@ -225,6 +228,28 @@ def test_fail_to_read_unknown_property( default_resource.config["user.valid-property"] = "ok" assert default_resource.config.data["user.valid-property"] == "ok" + def test_can_set_default_config( + self, + default_resource: 'libioc.Resource.DefaultResource' + ) -> None: + defaults_config_path = default_resource.config_handler.file + + default_resource.config.set("vnet", True); + default_resource.save() + with open(defaults_config_path, "r", encoding="UTF-8") as f: + data = json.load(f) + assert data["vnet"] == "yes" + assert len(data.keys()) == 1 + + default_resource.config.set("user.comment", "hi there!"); + default_resource.save() + with open(defaults_config_path, "r", encoding="UTF-8") as f: + data = json.load(f) + assert "user" in data.keys() + assert "comment" in data["user"].keys() + assert data["user"]["comment"] == "hi there!" + assert len(data.keys()) == 2 + class TestBrokenConfig(object): """Test the behavior of jails with invalid config."""