From e5c27231152e92900bab1ed9f8f0f47f31e4aadb Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 21 Apr 2024 16:41:31 +0200 Subject: [PATCH] More daemon configuration --- example/__init__.py | 2 +- nsconfig/cli.py | 5 ++++ nsconfig/core.py | 8 +++--- nsconfig/daemon/__init__.py | 51 +++++++++++++++++++++++++++++++++++-- nsconfig/daemon/bind.py | 28 ++++++++++++++++++-- 5 files changed, 86 insertions(+), 8 deletions(-) diff --git a/example/__init__.py b/example/__init__.py index aa37eca..d7dd219 100644 --- a/example/__init__.py +++ b/example/__init__.py @@ -4,7 +4,7 @@ from nsconfig.daemon.bind import NscDaemonBind nsc = Nsc( admin_email='admin@example.org', origin_server='ns.example.org', - daemon=NscDaemonBind(), + daemon=NscDaemonBind(control_command='echo'), ) for rev in ['10.1.0.0/16', '10.2.0.0/16', 'fd12:3456:789a::/48']: diff --git a/nsconfig/cli.py b/nsconfig/cli.py index 633a47e..84b2d08 100644 --- a/nsconfig/cli.py +++ b/nsconfig/cli.py @@ -58,12 +58,17 @@ def do_status(nsc: Nsc) -> None: def do_update(nsc: Nsc) -> None: + nsc.daemon.write_config() + for z in nsc.get_zones(): if isinstance(z, NscZonePrimary) and z.is_changed(): print(f'Updating zone {z.name} (serial {z.state.serial})') z.write_zone() + nsc.daemon.reload_zone(z) z.write_state() + nsc.daemon.reload_daemon() + def main(nsc: Nsc) -> None: parser = argparse.ArgumentParser(description='Configure name server') diff --git a/nsconfig/core.py b/nsconfig/core.py index b7d7de4..8a3bb74 100644 --- a/nsconfig/core.py +++ b/nsconfig/core.py @@ -356,7 +356,7 @@ class Nsc: state_dir: Path zone_dir: Path secondary_dir: Path - daemon: Optional['NscDaemon'] # Set by DaemonConfig class + daemon: 'NscDaemon' # Set by DaemonConfig class def __init__(self, directory: str = '.', @@ -376,9 +376,11 @@ class Nsc: self.secondary_dir = self.root_dir / 'secondary' self.secondary_dir.mkdir(parents=True, exist_ok=True) + if daemon is None: + from nsconfig.daemon import NscDaemonNull + daemon = NscDaemonNull() self.daemon = daemon - if daemon is not None: - daemon.setup(self) + daemon.setup(self) def add_zone(self, name: Optional[str] = None, diff --git a/nsconfig/daemon/__init__.py b/nsconfig/daemon/__init__.py index a981a0c..9140561 100644 --- a/nsconfig/daemon/__init__.py +++ b/nsconfig/daemon/__init__.py @@ -1,16 +1,63 @@ +from io import StringIO from pathlib import Path from typing import TextIO +import subprocess import sys -from nsconfig.core import Nsc +from nsconfig.core import Nsc, NscZone class NscDaemon: nsc: Nsc - config_path: Path + + def __init__(self) -> None: + pass def setup(self, nsc: Nsc) -> None: self.nsc = nsc def dump_config(self, file: TextIO = sys.stdout) -> None: pass + + def write_config(self) -> None: + pass + + def reload_zone(self, z: NscZone) -> None: + pass + + def reload_daemon(self) -> None: + pass + + def _install_config(self, path: Path, new_contents: str) -> bool: + try: + old_new_contents = path.read_text() + except FileNotFoundError: + old_new_contents = None + if new_contents == old_new_contents: + return False + else: + new_path = Path(str(path) + '.new') + with open(new_path, 'w') as f: + f.write(new_contents) + new_path.replace(path) + return True + + def _write_config(self, config_path: Path) -> bool: + string_stream = StringIO() + self.dump_config(string_stream) + if self._install_config(config_path, string_stream.getvalue()): + print('Wrote new daemon configuration') + return True + else: + print('Daemon configuration not changed') + return False + + def _run_command(self, argv, **kwargs) -> None: + res = subprocess.run(argv, **kwargs) + if res.returncode > 0: + print(f'Command failed: {argv}') + sys.exit(1) + + +class NscDaemonNull(NscDaemon): + pass diff --git a/nsconfig/daemon/bind.py b/nsconfig/daemon/bind.py index 13a9e0c..24a24fc 100644 --- a/nsconfig/daemon/bind.py +++ b/nsconfig/daemon/bind.py @@ -2,12 +2,22 @@ from pathlib import Path import sys from typing import TextIO -from nsconfig.core import NscZonePrimary, NscZoneSecondary +from nsconfig.core import NscZone, NscZonePrimary, NscZoneSecondary from nsconfig.daemon import NscDaemon class NscDaemonBind(NscDaemon): - config_path: Path = Path('named.conf.nsc') + config_path: Path + control_command: str + need_full_reload: bool + + def __init__(self, + config_file: str = 'named.conf.nsc', + control_command: str = 'rndc') -> None: + super().__init__() + self.config_path = Path(config_file) + self.control_command = control_command + self.need_full_reload = False def dump_config(self, file: TextIO = sys.stdout) -> None: file.write('# Domains managed by NSC\n') @@ -24,3 +34,17 @@ class NscDaemonBind(NscDaemon): else: raise NotImplementedError() file.write('}\n\n') + + def write_config(self) -> None: + if self._write_config(self.config_path): + self.need_full_reload = True + + def reload_zone(self, z: NscZone) -> None: + if isinstance(z, NscZonePrimary) and not self.need_full_reload: + print(f'Reloading zone {z.name}') + self._run_command([self.control_command, 'reload', z.name]) + + def reload_daemon(self) -> None: + if self.need_full_reload: + print('Reloading daemon') + self._run_command([self.control_command, 'reload']) -- 2.39.2