From: Martin Mares Date: Mon, 22 Apr 2024 10:59:18 +0000 (+0200) Subject: Implement alias zones X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=313eaf9b32245800e04d9c69b4ec29a791515af7;p=pynsc.git Implement alias zones --- diff --git a/TODO b/TODO index 9397d48..783852a 100644 --- a/TODO +++ b/TODO @@ -3,4 +3,3 @@ - Blackhole zones - DNSSEC - Logging -- Use dns.reversename.from_address? diff --git a/example/example_org.py b/example/example_org.py index ae034cf..493aae8 100644 --- a/example/example_org.py +++ b/example/example_org.py @@ -23,3 +23,5 @@ z.host('ns2', '10.2.0.1', 'fd12:3456:789a:2::1') .A('10.1.0.2', 'fd12:3456:789a:1::2') .MX(0, 'mail') .MX(10, 'mail.example.net')) + +nsc.add_zone('example.com', alias_for=z) diff --git a/nsconfig/cli.py b/nsconfig/cli.py index 84b2d08..bf502a2 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, NscZonePrimary, NscZoneSecondary +from nsconfig.core import Nsc, NscZonePrimary, NscZoneSecondary, NscZoneAlias def do_test(nsc: Nsc) -> None: @@ -12,6 +12,9 @@ def do_test(nsc: Nsc) -> None: print(f'Zone: {z.name}') print(f'Type: {z.zone_type.name}') if isinstance(z, NscZonePrimary): + if z.aliases: + aliases = ' '.join([alias.name for alias in z.aliases]) + print(f'Aliases: {aliases}') print(f'Old serial: {z.prev_state.serial}') print(f'Old hash: {z.prev_state.hash}') print(f'New serial: {z.state.serial}') @@ -22,6 +25,8 @@ def do_test(nsc: Nsc) -> None: z.dump(file=f) elif isinstance(z, NscZoneSecondary): print(f'Primary: {z.primary_server}') + elif isinstance(z, NscZoneAlias): + print(f'Alias for: {z.alias_for.name}') print() if nsc.daemon: @@ -38,21 +43,25 @@ def do_status(nsc: Nsc) -> None: table.set_deco(Texttable.HEADER) for z in nsc.get_zones(): - if not isinstance(z, NscZonePrimary): - table.add_row([z.name, 'secondary', "", "", "", ""]) - continue if z.is_changed(): action = '*' else: action = "" - table.add_row([ - z.name, - z.prev_state.serial, - z.prev_state.hash, - z.state.serial, - z.state.hash, - action, - ]) + if isinstance(z, NscZonePrimary): + table.add_row([ + z.name, + z.prev_state.serial, + z.prev_state.hash, + z.state.serial, + z.state.hash, + action, + ]) + elif isinstance(z, NscZoneSecondary): + table.add_row([z.name, 'secondary', "", "", "", action]) + elif isinstance(z, NscZoneAlias): + table.add_row([z.name, 'alias', "", "", "", action]) + else: + raise NotImplementedError() print(table.draw()) @@ -65,6 +74,8 @@ def do_update(nsc: Nsc) -> None: print(f'Updating zone {z.name} (serial {z.state.serial})') z.write_zone() nsc.daemon.reload_zone(z) + for alias in z.aliases: + nsc.daemon.reload_zone(z) z.write_state() nsc.daemon.reload_daemon() diff --git a/nsconfig/core.py b/nsconfig/core.py index f8c9661..50fa325 100644 --- a/nsconfig/core.py +++ b/nsconfig/core.py @@ -193,6 +193,7 @@ class NscZoneState: class ZoneType(Enum): primary = auto() secondary = auto() + alias = auto() class NscZone: @@ -216,6 +217,9 @@ class NscZone: def process(self) -> None: pass + def is_changed(self) -> bool: + return False + class NscZonePrimary(NscZone): zone: Zone @@ -223,6 +227,7 @@ class NscZonePrimary(NscZone): state_file: Path state: NscZoneState prev_state: NscZoneState + aliases: List['NscZoneAlias'] def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) @@ -235,6 +240,8 @@ class NscZonePrimary(NscZone): self.prev_state = NscZoneState() self.prev_state.load(self.state_file) + self.aliases = [] + self.zone = dns.zone.Zone(origin=self.name, rdclass=RdataClass.IN) self.update_soa() @@ -383,6 +390,21 @@ class NscZoneSecondary(NscZone): self.secondary_file = self.nsc.secondary_dir / self.safe_name +class NscZoneAlias(NscZone): + alias_for: NscZonePrimary + + def __init__(self, *args, alias_for=NscZonePrimary, **kwargs) -> None: + assert isinstance(alias_for, NscZonePrimary) + super().__init__(*args, **kwargs) + self.zone_type = ZoneType.alias + self.alias_for = alias_for + self.zone_file = alias_for.zone_file + alias_for.aliases.append(self) + + def is_changed(self) -> bool: + return self.alias_for.is_changed() + + class Nsc: start_time: datetime zones: Dict[str, NscZone] @@ -422,6 +444,7 @@ class Nsc: def add_zone(self, name: Optional[str] = None, reverse_for: str | IPNetwork | None = None, + alias_for: Optional[NscZonePrimary] = None, follow_primary: str | IPAddress | None = None, inherit_config: Optional[NscZoneConfig] = None, **kwargs) -> Zone: @@ -436,7 +459,10 @@ class Nsc: assert name not in self.zones z: NscZone - if follow_primary is None: + if alias_for is not None: + assert follow_primary is None + z = NscZoneAlias(self, name, reverse_for=reverse_for, alias_for=alias_for, inherit_config=inherit_config, **kwargs) + elif follow_primary is None: z = NscZonePrimary(self, name, reverse_for=reverse_for, inherit_config=inherit_config, **kwargs) else: if isinstance(follow_primary, str): diff --git a/nsconfig/daemon/bind.py b/nsconfig/daemon/bind.py index e639565..e5d7b34 100644 --- a/nsconfig/daemon/bind.py +++ b/nsconfig/daemon/bind.py @@ -2,7 +2,7 @@ from pathlib import Path import sys from typing import TextIO -from nsconfig.core import NscZone, NscZonePrimary, NscZoneSecondary +from nsconfig.core import NscZone, NscZonePrimary, NscZoneSecondary, NscZoneAlias from nsconfig.daemon import NscDaemon @@ -24,7 +24,7 @@ class NscDaemonBind(NscDaemon): 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): + if isinstance(z, NscZonePrimary) or isinstance(z, NscZoneAlias): file.write('\ttype master;\n') file.write(f'\tfile "{z.zone_file}";\n') elif isinstance(z, NscZoneSecondary): @@ -42,7 +42,7 @@ class NscDaemonBind(NscDaemon): self.need_full_reload = True def reload_zone(self, z: NscZone) -> None: - if isinstance(z, NscZonePrimary) and not self.need_full_reload: + if (isinstance(z, NscZonePrimary) or isinstance(z, NscZoneAlias)) and not self.need_full_reload: print(f'Reloading zone {z.name}') self._run_command([self.control_command, 'reload', z.name])