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']:
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')
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 = '.',
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,
+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
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')
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'])