From d01c8fe1bb65b9db249ffda1d39659fd4322dc64 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 21 Apr 2024 11:48:14 +0200 Subject: [PATCH] First bits of daemon configuration --- TODO | 1 + example/__init__.py | 2 ++ nsconfig/cli.py | 22 ++++++++++++++-------- nsconfig/core.py | 19 +++++++++++++++++-- nsconfig/daemon/__init__.py | 16 ++++++++++++++++ nsconfig/daemon/bind.py | 26 ++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 nsconfig/daemon/__init__.py create mode 100644 nsconfig/daemon/bind.py diff --git a/TODO b/TODO index afd48e8..cd0f4bb 100644 --- a/TODO +++ b/TODO @@ -6,3 +6,4 @@ - DNSSEC - Auto-generated files should contain a comment saying so - Automated generation of Null MX +- Bind: custom config diff --git a/example/__init__.py b/example/__init__.py index 6fd7fb5..aa37eca 100644 --- a/example/__init__.py +++ b/example/__init__.py @@ -1,8 +1,10 @@ from nsconfig import Nsc +from nsconfig.daemon.bind import NscDaemonBind nsc = Nsc( admin_email='admin@example.org', origin_server='ns.example.org', + daemon=NscDaemonBind(), ) 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 9d342c0..633a47e 100644 --- a/nsconfig/cli.py +++ b/nsconfig/cli.py @@ -2,7 +2,7 @@ import argparse from pathlib import Path from texttable import Texttable -from nsconfig.core import Nsc, ZoneType +from nsconfig.core import Nsc, NscZonePrimary, NscZoneSecondary def do_test(nsc: Nsc) -> None: @@ -11,7 +11,7 @@ def do_test(nsc: Nsc) -> None: for z in nsc.get_zones(): print(f'Zone: {z.name}') print(f'Type: {z.zone_type.name}') - if z.zone_type == ZoneType.primary: + if isinstance(z, NscZonePrimary): print(f'Old serial: {z.prev_state.serial}') print(f'Old hash: {z.prev_state.hash}') print(f'New serial: {z.state.serial}') @@ -20,10 +20,16 @@ def do_test(nsc: Nsc) -> None: print(f'Dumping to: {out_file}') with open(out_file, 'w') as f: z.dump(file=f) - else: + elif isinstance(z, NscZoneSecondary): print(f'Primary: {z.primary_server}') print() + if nsc.daemon: + conf_file = test_dir / nsc.daemon.config_path.name + print(f'Dumping daemon config to {conf_file}') + with open(conf_file, 'w') as f: + nsc.daemon.dump_config(file=f) + def do_status(nsc: Nsc) -> None: table = Texttable(max_width=0) @@ -32,13 +38,13 @@ def do_status(nsc: Nsc) -> None: table.set_deco(Texttable.HEADER) for z in nsc.get_zones(): - if z.zone_type != ZoneType.primary: + if not isinstance(z, NscZonePrimary): table.add_row([z.name, 'secondary', "", "", "", ""]) continue - if z.state.serial == z.prev_state.serial: - action = "" - else: + if z.is_changed(): action = '*' + else: + action = "" table.add_row([ z.name, z.prev_state.serial, @@ -53,7 +59,7 @@ def do_status(nsc: Nsc) -> None: def do_update(nsc: Nsc) -> None: for z in nsc.get_zones(): - if z.zone_type == ZoneType.primary and z.state.serial != z.prev_state.serial: + if isinstance(z, NscZonePrimary) and z.is_changed(): print(f'Updating zone {z.name} (serial {z.state.serial})') z.write_zone() z.write_state() diff --git a/nsconfig/core.py b/nsconfig/core.py index b61a02d..b7d7de4 100644 --- a/nsconfig/core.py +++ b/nsconfig/core.py @@ -21,7 +21,11 @@ import json from pathlib import Path import socket import sys -from typing import Optional, Dict, List, Self, Tuple, DefaultDict, TextIO +from typing import Optional, Dict, List, Self, Tuple, DefaultDict, TextIO, TYPE_CHECKING + + +if TYPE_CHECKING: + from nsconfig.daemon import NscDaemon IPAddress = IPv4Address | IPv6Address @@ -327,6 +331,9 @@ class NscZonePrimary(NscZone): def write_state(self) -> None: self.state.save(self.state_file) + def is_changed(self) -> bool: + return self.state.serial != self.prev_state.serial + class NscZoneSecondary(NscZone): primary_server: IPAddress @@ -349,8 +356,12 @@ class Nsc: state_dir: Path zone_dir: Path secondary_dir: Path + daemon: Optional['NscDaemon'] # Set by DaemonConfig class - def __init__(self, directory: str = '.', **kwargs) -> None: + def __init__(self, + directory: str = '.', + daemon: Optional['NscDaemon'] = None, + **kwargs) -> None: self.start_time = datetime.now() self.zones = {} self.default_zone_config = NscZoneConfig(**kwargs) @@ -365,6 +376,10 @@ class Nsc: self.secondary_dir = self.root_dir / 'secondary' self.secondary_dir.mkdir(parents=True, exist_ok=True) + self.daemon = daemon + if daemon is not None: + daemon.setup(self) + def add_zone(self, name: Optional[str] = None, reverse_for: str | IPNetwork | None = None, diff --git a/nsconfig/daemon/__init__.py b/nsconfig/daemon/__init__.py new file mode 100644 index 0000000..a981a0c --- /dev/null +++ b/nsconfig/daemon/__init__.py @@ -0,0 +1,16 @@ +from pathlib import Path +from typing import TextIO +import sys + +from nsconfig.core import Nsc + + +class NscDaemon: + nsc: Nsc + config_path: Path + + def setup(self, nsc: Nsc) -> None: + self.nsc = nsc + + def dump_config(self, file: TextIO = sys.stdout) -> None: + pass diff --git a/nsconfig/daemon/bind.py b/nsconfig/daemon/bind.py new file mode 100644 index 0000000..13a9e0c --- /dev/null +++ b/nsconfig/daemon/bind.py @@ -0,0 +1,26 @@ +from pathlib import Path +import sys +from typing import TextIO + +from nsconfig.core import NscZonePrimary, NscZoneSecondary +from nsconfig.daemon import NscDaemon + + +class NscDaemonBind(NscDaemon): + config_path: Path = Path('named.conf.nsc') + + def dump_config(self, file: TextIO = sys.stdout) -> None: + file.write('# Domains managed by NSC\n') + file.write('# This file was automatically generated by NSC, please do not edit manually.\n\n') + for z in self.nsc.get_zones(): + file.write(f'zone "{z.name}" in {{\n') # broken editor: }} + if isinstance(z, NscZonePrimary): + file.write('\ttype master;\n') + file.write(f'\tfile "{z.zone_file}";\n') + elif isinstance(z, NscZoneSecondary): + file.write('\ttype slave;\n') + file.write(f'\tfile "zone/{z.secondary_file}";\n') + file.write(f'\tmasters {{ {z.primary_server}; }};\n') + else: + raise NotImplementedError() + file.write('}\n\n') -- 2.39.2