mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-08-12 03:39:54 +00:00
feat: read includes from list of directories
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
[settings]
|
[settings]
|
||||||
; Relative path to directory with configuration files overrides. Overrides will be applied in alphabetic order.
|
; Relative path to directory with configuration files overrides. Overrides will be applied in alphabetic order.
|
||||||
include = ahriman.ini.d
|
include = ahriman.ini.d $HOME/.config/ahriman.ini.d
|
||||||
; Relative path to configuration used by logging package.
|
; Relative path to configuration used by logging package.
|
||||||
logging = ahriman.ini.d/logging.ini
|
logging = ahriman.ini.d/logging.ini
|
||||||
; Perform database migrations on the application start. Do not touch this option unless you know what you are doing.
|
; Perform database migrations on the application start. Do not touch this option unless you know what you are doing.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import argparse
|
import argparse
|
||||||
|
import os
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pwd import getpwuid
|
from pwd import getpwuid
|
||||||
@@ -161,8 +162,9 @@ class Setup(Handler):
|
|||||||
if args.generate_salt:
|
if args.generate_salt:
|
||||||
configuration.set_option("auth", "salt", User.generate_password(20))
|
configuration.set_option("auth", "salt", User.generate_password(20))
|
||||||
|
|
||||||
(root.include / "00-setup-overrides.ini").unlink(missing_ok=True) # remove old-style configuration
|
include_path = next(path for path in root.include if os.access(path, os.W_OK))
|
||||||
target = root.include / f"00-setup-overrides-{repository_id.id}.ini"
|
(include_path / "00-setup-overrides.ini").unlink(missing_ok=True) # remove old-style configuration
|
||||||
|
target = include_path / f"00-setup-overrides-{repository_id.id}.ini"
|
||||||
with target.open("w", encoding="utf8") as ahriman_configuration:
|
with target.open("w", encoding="utf8") as ahriman_configuration:
|
||||||
configuration.write(ahriman_configuration)
|
configuration.write(ahriman_configuration)
|
||||||
|
|
||||||
|
|||||||
@@ -109,14 +109,14 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
return repository_id.architecture
|
return repository_id.architecture
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def include(self) -> Path:
|
def include(self) -> list[Path]:
|
||||||
"""
|
"""
|
||||||
get full path to include directory
|
get full path to include directory(ies)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Path: path to directory with configuration includes
|
list[Path]: path to directory with configuration includes
|
||||||
"""
|
"""
|
||||||
return self.getpath("settings", "include")
|
return self.getpathlist("settings", "include")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def logging_path(self) -> Path:
|
def logging_path(self) -> Path:
|
||||||
@@ -325,23 +325,19 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
section, key = name.rsplit(":", maxsplit=1)
|
section, key = name.rsplit(":", maxsplit=1)
|
||||||
self.set_option(section, key, value)
|
self.set_option(section, key, value)
|
||||||
|
|
||||||
def load_includes(self, path: Path | None = None) -> None:
|
def load_includes(self) -> None:
|
||||||
"""
|
"""
|
||||||
load configuration includes from specified path
|
load configuration includes from specified path
|
||||||
|
|
||||||
Args:
|
|
||||||
path(Path | None, optional): path to directory with include files. If none set, the default path will be
|
|
||||||
used (Default value = None)
|
|
||||||
"""
|
"""
|
||||||
self.includes = [] # reset state
|
self.includes = [] # reset state
|
||||||
|
|
||||||
try:
|
try:
|
||||||
path = path or self.include
|
for path in self.include: # pylint: disable=not-an-iterable
|
||||||
for include in sorted(path.glob("*.ini")):
|
for include in sorted(path.glob("*.ini")):
|
||||||
if include == self.logging_path:
|
if include == self.logging_path:
|
||||||
continue # we don't want to load logging explicitly
|
continue # we don't want to load logging explicitly
|
||||||
self.read(include)
|
self.read(include)
|
||||||
self.includes.append(include)
|
self.includes.append(include)
|
||||||
except (FileNotFoundError, configparser.NoOptionError, configparser.NoSectionError):
|
except (FileNotFoundError, configparser.NoOptionError, configparser.NoSectionError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,13 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
|||||||
"required": True,
|
"required": True,
|
||||||
},
|
},
|
||||||
"include": {
|
"include": {
|
||||||
"type": "path",
|
"type": "list",
|
||||||
"coerce": "absolute_path",
|
"coerce": "list",
|
||||||
"path_exists": True,
|
"schema": {
|
||||||
"path_type": "dir",
|
"type": "path",
|
||||||
|
"coerce": "absolute_path",
|
||||||
|
"path_type": "dir",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"logging": {
|
"logging": {
|
||||||
"type": "path",
|
"type": "path",
|
||||||
|
|||||||
@@ -190,5 +190,5 @@ class Validator(RootValidator):
|
|||||||
{"type": "string"}
|
{"type": "string"}
|
||||||
"""
|
"""
|
||||||
fn = getattr(value, f"is_{constraint}")
|
fn = getattr(value, f"is_{constraint}")
|
||||||
if not fn():
|
if value.exists() and not fn():
|
||||||
self._error(field, f"Path {value} must be type of {constraint}")
|
self._error(field, f"Path {value} must be type of {constraint}")
|
||||||
|
|||||||
@@ -137,7 +137,11 @@ def test_configuration_create_ahriman(args: argparse.Namespace, configuration: C
|
|||||||
MockCall("auth", "salt", pytest.helpers.anyvar(str, strict=True)),
|
MockCall("auth", "salt", pytest.helpers.anyvar(str, strict=True)),
|
||||||
])
|
])
|
||||||
write_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
write_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
||||||
remove_mock.assert_called_once_with(configuration.include / "00-setup-overrides.ini", missing_ok=True)
|
remove_mock.assert_called_once_with(
|
||||||
|
next(
|
||||||
|
path for path in configuration.include) /
|
||||||
|
"00-setup-overrides.ini",
|
||||||
|
missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
def test_configuration_create_ahriman_no_multilib(args: argparse.Namespace, configuration: Configuration,
|
def test_configuration_create_ahriman_no_multilib(args: argparse.Namespace, configuration: Configuration,
|
||||||
|
|||||||
@@ -374,17 +374,16 @@ def test_load_environment(configuration: Configuration) -> None:
|
|||||||
assert configuration.get("section:identifier", "key") == "value2"
|
assert configuration.get("section:identifier", "key") == "value2"
|
||||||
|
|
||||||
|
|
||||||
def test_load_includes(mocker: MockerFixture) -> None:
|
def test_load_includes(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must load includes
|
must load includes
|
||||||
"""
|
"""
|
||||||
mocker.patch.object(Configuration, "logging_path", Path("logging"))
|
mocker.patch.object(Configuration, "logging_path", Path("logging"))
|
||||||
read_mock = mocker.patch("ahriman.core.configuration.Configuration.read")
|
read_mock = mocker.patch("ahriman.core.configuration.Configuration.read")
|
||||||
glob_mock = mocker.patch("pathlib.Path.glob", autospec=True, return_value=[Path("include"), Path("logging")])
|
glob_mock = mocker.patch("pathlib.Path.glob", autospec=True, return_value=[Path("include"), Path("logging")])
|
||||||
configuration = Configuration()
|
|
||||||
|
|
||||||
configuration.load_includes(Path("path"))
|
configuration.load_includes()
|
||||||
glob_mock.assert_called_once_with(Path("path"), "*.ini")
|
glob_mock.assert_called_once_with(configuration.path.absolute().parent, "*.ini")
|
||||||
read_mock.assert_called_once_with(Path("include"))
|
read_mock.assert_called_once_with(Path("include"))
|
||||||
assert configuration.includes == [Path("include")]
|
assert configuration.includes == [Path("include")]
|
||||||
|
|
||||||
@@ -415,17 +414,6 @@ def test_load_includes_no_section() -> None:
|
|||||||
configuration.load_includes()
|
configuration.load_includes()
|
||||||
|
|
||||||
|
|
||||||
def test_load_includes_default_path(mocker: MockerFixture) -> None:
|
|
||||||
"""
|
|
||||||
must load includes from default path
|
|
||||||
"""
|
|
||||||
mocker.patch.object(Configuration, "include", Path("path"))
|
|
||||||
glob_mock = mocker.patch("pathlib.Path.glob", autospec=True, return_value=[])
|
|
||||||
|
|
||||||
Configuration().load_includes()
|
|
||||||
glob_mock.assert_called_once_with(Path("path"), "*.ini")
|
|
||||||
|
|
||||||
|
|
||||||
def test_merge_sections_missing(configuration: Configuration) -> None:
|
def test_merge_sections_missing(configuration: Configuration) -> None:
|
||||||
"""
|
"""
|
||||||
must merge create section if not exists
|
must merge create section if not exists
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ def test_validate_path_type(validator: Validator, mocker: MockerFixture) -> None
|
|||||||
"""
|
"""
|
||||||
error_mock = mocker.patch("ahriman.core.configuration.validator.Validator._error")
|
error_mock = mocker.patch("ahriman.core.configuration.validator.Validator._error")
|
||||||
|
|
||||||
|
validator._validate_path_type("file", "field", Path("42"))
|
||||||
|
|
||||||
|
mocker.patch("pathlib.Path.exists", return_value=True)
|
||||||
|
|
||||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||||
validator._validate_path_type("file", "field", Path("1"))
|
validator._validate_path_type("file", "field", Path("1"))
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ Base configuration settings.
|
|||||||
|
|
||||||
* ``apply_migrations`` - perform database migrations on the application start, boolean, optional, default ``yes``. Useful if you are using git version. Note, however, that this option must be changed only if you know what to do and going to handle migrations manually.
|
* ``apply_migrations`` - perform database migrations on the application start, boolean, optional, default ``yes``. Useful if you are using git version. Note, however, that this option must be changed only if you know what to do and going to handle migrations manually.
|
||||||
* ``database`` - path to the application SQLite database, string, required.
|
* ``database`` - path to the application SQLite database, string, required.
|
||||||
* ``include`` - path to directory with configuration files overrides, string, optional. Files will be read in alphabetical order.
|
* ``include`` - path to directories with configuration files overrides, space separated list of strings, optional. Files will be read in alphabetical order.
|
||||||
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
|
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
|
||||||
|
|
||||||
``alpm:*`` groups
|
``alpm:*`` groups
|
||||||
|
|||||||
Reference in New Issue
Block a user