diff --git a/octodns/source/tinydns.py b/octodns/source/tinydns.py index 1f864f7..100f482 100755 --- a/octodns/source/tinydns.py +++ b/octodns/source/tinydns.py @@ -16,12 +16,16 @@ from .base import BaseSource class TinyDnsBaseSource(BaseSource): SUPPORTS_GEO = False SUPPORTS_DYNAMIC = False - SUPPORTS = set(('A', 'CNAME', 'MX', 'NS', 'TXT', 'AAAA')) def __init__(self, id, default_ttl=3600): super().__init__(id) self.default_ttl = default_ttl + @property + def SUPPORTS(self): + # All record types, including those registered by 3rd party modules + return set(Record.registered_types().keys()) + def _records_for_at(self, zone, name, lines, arpa=False): # @fqdn:ip:x:dist:ttl:timestamp:lo # MX (and optional A) @@ -38,7 +42,7 @@ class TinyDnsBaseSource(BaseSource): ttl = self.default_ttl for line in lines: try: - ttl = int(lines[0][4]) + ttl = int(line[4]) break except IndexError: pass @@ -61,7 +65,7 @@ class TinyDnsBaseSource(BaseSource): # if we have an IP then we need to create an A for the MX ip = line[1] - if ip: + if ip and zone.owns('A', mx): yield 'A', mx, ttl, [ip] values.append({'preference': dist, 'exchange': mx}) @@ -88,7 +92,7 @@ class TinyDnsBaseSource(BaseSource): ttl = self.default_ttl for line in lines: try: - ttl = int(lines[0][2]) + ttl = int(line[2]) break except IndexError: pass @@ -163,7 +167,7 @@ class TinyDnsBaseSource(BaseSource): ttl = self.default_ttl for line in lines: try: - ttl = int(lines[0][3]) + ttl = int(line[3]) break except IndexError: pass @@ -180,7 +184,7 @@ class TinyDnsBaseSource(BaseSource): # if we have an IP then we need to create an A for the MX ip = line[1] - if ip: + if ip and zone.owns('A', ns): yield 'A', ns, ttl, [ip] values.append(ns) @@ -212,7 +216,7 @@ class TinyDnsBaseSource(BaseSource): ttl = self.default_ttl for line in lines: try: - ttl = int(lines[0][2]) + ttl = int(line[2]) break except IndexError: pass @@ -241,7 +245,7 @@ class TinyDnsBaseSource(BaseSource): ttl = self.default_ttl for line in lines: try: - ttl = int(lines[0][2]) + ttl = int(line[2]) break except IndexError: pass @@ -272,13 +276,76 @@ class TinyDnsBaseSource(BaseSource): ttl = self.default_ttl for line in lines: try: - ttl = int(lines[0][2]) + ttl = int(line[2]) break except IndexError: pass yield 'AAAA', name, ttl, ips + def _records_for_S(self, zone, name, lines, arpa=False): + # Sfqdn:ip:x:port:priority:weight:ttl:timestamp:lo + # SRV + + if arpa: + # no arpa + return [] + + if not zone.owns('SRV', name): + # if name doesn't live under our zone there's nothing for us to do + return + + # see if we can find a ttl on any of the lines, first one wins + ttl = self.default_ttl + for line in lines: + try: + ttl = int(line[6]) + break + except IndexError: + pass + + values = [] + for line in lines: + target = line[2] + # if there's a . in the mx we hit a special case and use it as-is + if '.' not in target: + # otherwise we treat it as the MX hostnam and construct the rest + target = f'{target}.srv.{zone.name}' + elif target[-1] != '.': + target = f'{target}.' + + # if we have an IP then we need to create an A for the SRV + # has to be present, but can be empty + ip = line[1] + if ip and zone.owns('A', target): + yield 'A', target, ttl, [ip] + + # required + port = int(line[3]) + + # optional, default 0 + try: + priority = int(line[4] or 0) + except IndexError: + priority = 0 + + # optional, default 0 + try: + weight = int(line[5] or 0) + except IndexError: + weight = 0 + + values.append( + { + 'priority': priority, + 'weight': weight, + 'port': port, + 'target': target, + } + ) + + yield 'SRV', name, ttl, values + def _records_for_six(self, zone, name, lines, arpa=False): # 6fqdn:ip:ttl:timestamp:lo # AAAA (arpa False) & PTR (arpa True) @@ -297,9 +364,9 @@ class TinyDnsBaseSource(BaseSource): '&': _records_for_amp, # NS '\'': _records_for_quote, # TXT '3': _records_for_three, # AAAA + 'S': _records_for_S, # SRV '6': _records_for_six, # AAAA # TODO: - #'S': _records_for_S, # SRV # Sfqdn:ip:x:port:priority:weight:ttl:timestamp:lo #':': _record_for_semicolon # arbitrary # :fqdn:n:rdata:ttl:timestamp:lo diff --git a/tests/test_octodns_source_tinydns.py b/tests/test_octodns_source_tinydns.py index 47781f2..81129b0 100644 --- a/tests/test_octodns_source_tinydns.py +++ b/tests/test_octodns_source_tinydns.py @@ -17,7 +17,7 @@ class TestTinyDnsFileSource(TestCase): def test_populate_normal(self): got = Zone('example.com.', []) self.source.populate(got) - self.assertEqual(25, len(got.records)) + self.assertEqual(28, len(got.records)) expected = Zone('example.com.', []) for name, data in ( @@ -140,6 +140,43 @@ class TestTinyDnsFileSource(TestCase): 'values': ['ns5.ns.example.com.', 'ns6.ns.example.com.'], }, ), + ( + '_a._tcp', + { + 'type': 'SRV', + 'ttl': 43, + 'values': [ + { + 'priority': 0, + 'weight': 0, + 'port': 8888, + 'target': 'target.srv.example.com.', + }, + { + 'priority': 10, + 'weight': 50, + 'port': 8080, + 'target': 'target.somewhere.else.', + }, + ], + }, + ), + ('target.srv', {'type': 'A', 'ttl': 43, 'value': '56.57.58.59'}), + ( + '_b._tcp', + { + 'type': 'SRV', + 'ttl': 3600, + 'values': [ + { + 'priority': 0, + 'weight': 0, + 'port': 9999, + 'target': 'target.srv.example.com.', + } + ], + }, + ), ): record = Record.new(expected, name, data) expected.add_record(record) @@ -216,4 +253,4 @@ class TestTinyDnsFileSource(TestCase): got = Zone('example.com.', ['sub']) self.source.populate(got) # we don't see one www.sub.example.com. record b/c it's in a sub - self.assertEqual(24, len(got.records)) + self.assertEqual(27, len(got.records)) diff --git a/tests/zones/tinydns/example.com b/tests/zones/tinydns/example.com index fcb38d4..9cb1eb8 100755 --- a/tests/zones/tinydns/example.com +++ b/tests/zones/tinydns/example.com @@ -61,3 +61,9 @@ Ccname.other.foo:www.other.foo 6ipv6-6.example.com:2a021348017cd5d0002419fffef35743 'semicolon.example.com:v=DKIM1; k=rsa; p=blah:300 + +# SRV +S_a._tcp.example.com:56.57.58.59:target:8888 +S_a._tcp.example.com::target.somewhere.else:8080:10:50:43 +# TODO: add an IP so it tries to create a record that already exists +S_b._tcp.example.com::target.srv.example.com.:9999