- Blackhole zones
- DNSSEC
- Logging
-- Use dns.reversename.from_address?
.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)
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:
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}')
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:
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())
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()
class ZoneType(Enum):
primary = auto()
secondary = auto()
+ alias = auto()
class NscZone:
def process(self) -> None:
pass
+ def is_changed(self) -> bool:
+ return False
+
class NscZonePrimary(NscZone):
zone: Zone
state_file: Path
state: NscZoneState
prev_state: NscZoneState
+ aliases: List['NscZoneAlias']
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
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()
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]
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:
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):
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
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):
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])