]> mj.ucw.cz Git - pynsc.git/commitdiff
Implement alias zones
authorMartin Mares <mj@ucw.cz>
Mon, 22 Apr 2024 10:59:18 +0000 (12:59 +0200)
committerMartin Mares <mj@ucw.cz>
Mon, 22 Apr 2024 10:59:18 +0000 (12:59 +0200)
TODO
example/example_org.py
nsconfig/cli.py
nsconfig/core.py
nsconfig/daemon/bind.py

diff --git a/TODO b/TODO
index 9397d48b71eee55287eaf2411d7debb8463322f6..783852aee21096c03dc9397a3192744505566e7b 100644 (file)
--- a/TODO
+++ b/TODO
@@ -3,4 +3,3 @@
 - Blackhole zones
 - DNSSEC
 - Logging
-- Use dns.reversename.from_address?
index ae034cff69f568983361c010492d3ec111dbfa63..493aae82a542de91a700d4e89fb0ef83fdfddddf 100644 (file)
@@ -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)
index 84b2d08031021d09ee81515e9e53694ce737a958..bf502a24541902e237854b96a00fc5ac18459d8b 100644 (file)
@@ -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()
index f8c96615c1246d0dfb4df1d9800189e42c1effd8..50fa325b526e9ae925d1b4791cc233fc2a4764e3 100644 (file)
@@ -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):
index e639565ec9f7696e9877419d075d9bd1eba1f5ba..e5d7b347738bbf4063d5962dbb3c572a9318b484 100644 (file)
@@ -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])