diff --git a/manager/knot_resolver_manager/datamodel/config_schema.py b/manager/knot_resolver_manager/datamodel/config_schema.py index f61806c201ee811db49d9b854d17faa849566ad1..478b71cbf507a0defa9489b726de6243f4cdc08c 100644 --- a/manager/knot_resolver_manager/datamodel/config_schema.py +++ b/manager/knot_resolver_manager/datamodel/config_schema.py @@ -23,7 +23,8 @@ from knot_resolver_manager.datamodel.rpz_schema import RPZSchema from knot_resolver_manager.datamodel.slice_schema import SliceSchema from knot_resolver_manager.datamodel.static_hints_schema import StaticHintsSchema from knot_resolver_manager.datamodel.stub_zone_schema import StubZoneSchema -from knot_resolver_manager.datamodel.types import AbsoluteDir, IntPositive +from knot_resolver_manager.datamodel.types import IntPositive +from knot_resolver_manager.datamodel.types.files import UncheckedPath from knot_resolver_manager.datamodel.view_schema import ViewSchema from knot_resolver_manager.datamodel.webmgmt_schema import WebmgmtSchema from knot_resolver_manager.utils.modeling import ConfigSchema @@ -112,7 +113,7 @@ class KresConfig(ConfigSchema): version: int = 1 nsid: Optional[str] = None hostname: Optional[str] = None - rundir: AbsoluteDir = AbsoluteDir("/var/run/knot-resolver") + rundir: UncheckedPath = UncheckedPath("/var/run/knot-resolver") workers: Union[Literal["auto"], IntPositive] = IntPositive(1) max_workers: IntPositive = IntPositive(_default_max_worker_count()) management: ManagementSchema = ManagementSchema({"unix-socket": "./manager.sock"}) @@ -137,7 +138,7 @@ class KresConfig(ConfigSchema): nsid: Optional[str] hostname: str - rundir: AbsoluteDir + rundir: UncheckedPath workers: IntPositive max_workers: IntPositive management: ManagementSchema @@ -201,3 +202,20 @@ class KresConfig(ConfigSchema): # it should be removed and relative path used instead as soon as issue # https://gitlab.nic.cz/knot/knot-resolver/-/issues/720 is fixed return _MAIN_TEMPLATE.render(cfg=self, cwd=os.getcwd()) # pyright: reportUnknownMemberType=false + + +def get_rundir_without_validation(data: Dict[str, Any]) -> UncheckedPath: + """ + Without fully parsing, try to get a rundir from a raw config data. When it fails, + attempts a full validation to produce a good error message. + + Used for initial manager startup. + """ + + if "rundir" in data: + rundir = data["rundir"] + else: + _ = KresConfig(data) # this should throw a descriptive error + assert False + + return UncheckedPath(rundir) diff --git a/manager/knot_resolver_manager/datamodel/types/files.py b/manager/knot_resolver_manager/datamodel/types/files.py index 73db8b70dc0b3b57059a5a6a187be775c81cafc0..1672132ee254bcab9194807e62edc0ac3ba0c745 100644 --- a/manager/knot_resolver_manager/datamodel/types/files.py +++ b/manager/knot_resolver_manager/datamodel/types/files.py @@ -77,7 +77,7 @@ class Dir(UncheckedPath): ) -> None: super().__init__(source_value, parents=parents, object_path=object_path) if not self._value.is_dir(): - raise ValueError("path does not point to an existing directory") + raise ValueError(f"path '{self._value}' does not point to an existing directory") class AbsoluteDir(Dir): @@ -124,7 +124,7 @@ class FilePath(UncheckedPath): super().__init__(source_value, parents=parents, object_path=object_path) p = self._value.parent if not p.exists() or not p.is_dir(): - raise ValueError("path does not point inside an existing directory") + raise ValueError(f"path '{self._value}' does not point inside an existing directory") if self._value.is_dir(): raise ValueError("path points to a directory when we expected a file") diff --git a/manager/knot_resolver_manager/server.py b/manager/knot_resolver_manager/server.py index 1126798ecbea77df58498912e31199a354f8d652..a837df550fa3be05b2f20861b2cd56f1ae16017d 100644 --- a/manager/knot_resolver_manager/server.py +++ b/manager/knot_resolver_manager/server.py @@ -23,7 +23,7 @@ from knot_resolver_manager import log, statistics from knot_resolver_manager.compat import asyncio as asyncio_compat from knot_resolver_manager.config_store import ConfigStore from knot_resolver_manager.constants import DEFAULT_MANAGER_CONFIG_FILE, PID_FILE_NAME, init_user_constants -from knot_resolver_manager.datamodel.config_schema import KresConfig +from knot_resolver_manager.datamodel.config_schema import KresConfig, get_rundir_without_validation from knot_resolver_manager.datamodel.management_schema import ManagementSchema from knot_resolver_manager.exceptions import CancelStartupExecInsteadException, KresManagerException from knot_resolver_manager.kresd_controller import get_best_controller_implementation @@ -389,12 +389,13 @@ async def _deny_working_directory_changes(config_old: KresConfig, config_new: Kr def _set_working_directory(config_raw: Dict[str, Any]) -> None: - config = KresConfig(config_raw) + rundir = get_rundir_without_validation(config_raw) - if not config.rundir.to_path().exists(): - raise KresManagerException(f"`rundir` directory ({config.rundir}) does not exist!") + if not rundir.to_path().exists(): + raise KresManagerException(f"`rundir` directory ({rundir}) does not exist!") - os.chdir(config.rundir.to_path()) + logger.info("changing working directory to rundir at '%s'", rundir.to_path().absolute()) + os.chdir(rundir.to_path()) def _lock_working_directory(attempt: int = 0) -> None: @@ -469,11 +470,9 @@ async def start_server(config: Union[Path, Dict[str, Any]] = DEFAULT_MANAGER_CON config_raw = await _load_raw_config(config) # We want to change cwd as soon as possible. Especially before any real config validation, because cwd - # is used for resolving relative paths. Thats also a reason, why in practice, we validate the config twice. - # Once when setting up the cwd just to read the `rundir` property. When cwd is set, we do it again to resolve - # all paths correctly. - # Note: the first config validation is done here - therefore all initial config validation errors will - # originate from here. + # is used for resolving relative paths. If we fail to read rundir from unparsed config, a full validation + # error will come from here. If we are successfull, full validation will be done further on when initializing + # the config store. _set_working_directory(config_raw) # We don't want more than one manager in a single working directory. So we lock it with a PID file.