start actually writing something
authorRalf Jung <post@ralfj.de>
Sun, 9 Nov 2014 17:05:05 +0000 (18:05 +0100)
committerRalf Jung <post@ralfj.de>
Sun, 9 Nov 2014 17:05:05 +0000 (18:05 +0100)
.gitignore [new file with mode: 0644]
zone-maker
zonemaker/zone.py

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..087ec42
--- /dev/null
@@ -0,0 +1,2 @@
+__pycache__
+ralfj.de.py
index 100b17a7932caa0c09568f1b18cb0ee8e7b6fa0e..2d52a789ed4b7148ac6298b7c4aaabc1b4616519 100755 (executable)
@@ -1,5 +1,7 @@
 #!/usr/bin/python3
 import sys, os
 #!/usr/bin/python3
 import sys, os
+from zonemaker.zone import Zone
+from typing import Sequence
 
 def load_module(name, path, write_bytecode = False):
     import importlib.machinery
 
 def load_module(name, path, write_bytecode = False):
     import importlib.machinery
@@ -9,10 +11,9 @@ def load_module(name, path, write_bytecode = False):
     sys.dont_write_bytecode = old_val
     return module
 
     sys.dont_write_bytecode = old_val
     return module
 
-def make_zone(filename):
+def make_zone(filename: str) -> None:
     zonefile = load_module(os.path.basename(filename), filename)
     zonefile = load_module(os.path.basename(filename), filename)
-    zones = zonefile.__zones__
-    # TODO do something more clever with the zones
+    zones = zonefile.__zones__ # type: Sequence[Zone]
     for zone in zones:
         zone.write(sys.stdout)
 
     for zone in zones:
         zone.write(sys.stdout)
 
index f8390a41f82bf0ca074d99848ebe0603ec5fabe6..c30cab165b996399a157ba1aa045ac3a61b7a75a 100644 (file)
@@ -1,24 +1,48 @@
 import re
 from ipaddress import IPv4Address, IPv6Address
 import re
 from ipaddress import IPv4Address, IPv6Address
-from typing import List, Dict, Any
+from typing import List, Dict, Any, Iterator
 
 
 second = 1
 minute = 60*second
 hour = 60*minute
 day = 24*hour
 
 
 second = 1
 minute = 60*second
 hour = 60*minute
 day = 24*hour
+week = 7*day
 
 
-def hostname(name: str) -> str:
+def check_hostname(name: str) -> str:
     # check hostname for validity
     label = r'[a-zA-Z90-9]([a-zA-Z90-9-]{0,61}[a-zA-Z90-9])?' # must not start or end with hyphen
     pattern = r'^{0}(\.{0})*\.?'.format(label)
     # check hostname for validity
     label = r'[a-zA-Z90-9]([a-zA-Z90-9-]{0,61}[a-zA-Z90-9])?' # must not start or end with hyphen
     pattern = r'^{0}(\.{0})*\.?'.format(label)
-    print(pattern)
     if re.match(pattern, name):
         return name
     raise Exception(name+" is not a valid hostname")
 
     if re.match(pattern, name):
         return name
     raise Exception(name+" is not a valid hostname")
 
+def abs_hostname(name: str, root: str = None) -> str:
+    if name.endswith('.'):
+        return name
+    if root is None:
+        raise Exception("Name {0} was expected to be absolute, but it is not".format(name))
+    if not root.endswith('.'):
+        raise Exception("Root {0} was expected to be absolute, but it is not".format(name))
+    return name+"."+root
+
+def time(time: int) -> str:
+    if time == 0:
+        return "0"
+    elif time % week == 0:
+        return str(time//week)+"w"
+    elif time % day == 0:
+        return str(time//day)+"d"
+    elif time % hour == 0:
+        return str(time//hour)+"h"
+    elif time % minute == 0:
+        return str(time//minute)+"m"
+    else:
+        return str(time)
+
 class Address:
 class Address:
-    def __init__(self, IPv4: str = None, IPv6: str = None) -> None:
+    # mypy does not know about the ipaddress types, so leave this class unannotated for now
+    def __init__(self, IPv4 = None, IPv6 = None) -> None:
         self._IPv4 = None if IPv4 is None else IPv4Address(IPv4)
         self._IPv6 = None if IPv6 is None else IPv6Address(IPv6)
     
         self._IPv4 = None if IPv4 is None else IPv4Address(IPv4)
         self._IPv6 = None if IPv6 is None else IPv6Address(IPv6)
     
@@ -35,37 +59,58 @@ class Name:
 
 class Service:
     def __init__(self, SRV: str = None, TLSA: str=None) -> None:
 
 class Service:
     def __init__(self, SRV: str = None, TLSA: str=None) -> None:
-        self._SRV = SRV
+        self._SRV = None if SRV is None else check_hostname(SRV)
         self._TLSA = TLSA
 
 class CName:
     def __init__(self, name: str) -> None:
         self._TLSA = TLSA
 
 class CName:
     def __init__(self, name: str) -> None:
-        self._name = name
+        self._name = check_hostname(name)
 
 class Delegation():
     def __init__(self, NS: str, DS: str = None) -> None:
         pass
 
 
 class Delegation():
     def __init__(self, NS: str, DS: str = None) -> None:
         pass
 
+class RR():
+    def __init__(self, owner: str, TTL: int, recordType: str, data: str) -> None:
+        assert owner.endswith('.') # users have to make this absolute first
+        self._owner = owner
+        self._TTL = TTL
+        self._recordType = recordType
+        self._data = data
+    
+    def __str__(self) -> str:
+        return "{0}\t{1}\t{2}\t{3}".format(self._owner, self._TTL, self._recordType, self._data)
+
 class Zone:
     def __init__(self, name: str, mail: str, NS: List[str],
 class Zone:
     def __init__(self, name: str, mail: str, NS: List[str],
-                 secondary_refresh: int, secondary_retry: int, secondary_discard: int,
+                 secondary_refresh: int, secondary_retry: int, secondary_expire: int,
                  NX_TTL: int = None, A_TTL: int = None, other_TTL: int = None,
                  domains: Dict[str, Any] = {}) -> None:
                  NX_TTL: int = None, A_TTL: int = None, other_TTL: int = None,
                  domains: Dict[str, Any] = {}) -> None:
-        self._name = hostname(name)
+        self._name = check_hostname(name)
         if not mail.endswith('.'): raise Exception("Mail must be absolute, end with a dot")
         atpos = mail.find('@')
         if atpos < 0 or atpos > mail.find('.'): raise Exception("Mail must contain an @ before the first dot")
         if not mail.endswith('.'): raise Exception("Mail must be absolute, end with a dot")
         atpos = mail.find('@')
         if atpos < 0 or atpos > mail.find('.'): raise Exception("Mail must contain an @ before the first dot")
-        self._mail = hostname(mail.replace('@', '.', 1))
-        self._NS = list(map(hostname, NS))
+        self._mail = check_hostname(mail.replace('@', '.', 1))
+        self._NS = list(map(check_hostname, NS))
         
         
-        self._secondary_refresh = secondary_refresh
-        self._secondary_retry = secondary_retry
-        self._secondary_discard = secondary_discard
+        self._refresh = secondary_refresh
+        self._retry = secondary_retry
+        self._expire = secondary_expire
         
         assert other_TTL is not None
         self._NX_TTL = other_TTL if NX_TTL is None else NX_TTL
         self._A_TTL = other_TTL if A_TTL is None else A_TTL
         self._other_TTL = other_TTL
     
         
         assert other_TTL is not None
         self._NX_TTL = other_TTL if NX_TTL is None else NX_TTL
         self._A_TTL = other_TTL if A_TTL is None else A_TTL
         self._other_TTL = other_TTL
     
-    def write(self, file):
-        raise NotImplementedError()
+    def generate_rrs(self) -> Iterator[RR]:
+        serial = -1
+        yield (RR(abs_hostname(self._name), self._other_TTL, 'SOA',
+                  '{NS} {mail} ({serial} {refresh} {retry} {expire} {NX_TTL})'.format(
+                      NS=self._NS[0], mail=self._mail, serial=serial,
+                      refresh=time(self._refresh), retry=time(self._retry), expire=time(self._expire),
+                      NX_TTL=time(self._NX_TTL))
+                  ))
+    
+    def write(self, file:Any) -> None:
+        for rr in self.generate_rrs():
+            print(rr)