pvl.hosts.zone: cleanup and split pvl.hosts-forward and pvl.hosts-reverse from pvl.hosts-dns
authorTero Marttila <tero.marttila@aalto.fi>
Tue, 24 Feb 2015 21:38:12 +0200
changeset 458 600ad9eb6f25
parent 457 1e925a1cc8de
child 459 927b9caf3be6
pvl.hosts.zone: cleanup and split pvl.hosts-forward and pvl.hosts-reverse from pvl.hosts-dns
bin/pvl.hosts-dns
bin/pvl.hosts-forward
bin/pvl.hosts-reverse
pvl/hosts/zone.py
--- a/bin/pvl.hosts-dns	Tue Feb 24 21:37:36 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,303 +0,0 @@
-#!/usr/bin/env python
-
-import pvl.args
-import pvl.hosts
-import pvl.dns.zone
-
-import ipaddr
-import logging; log = logging.getLogger('pvl.hosts-dns')
-import optparse 
-
-def process_hosts_alias (options, origin, host_domain, alias, host) :
-    """
-        Resolve alias@domain within given zone.
-    """
-
-    if host_domain :
-        alias = pvl.dns.join(alias, host_domain)
-    else :
-        raise ValueError("no domain given for host %s alias %s" % (host, alias, ))
-
-    if alias.endswith('.' + origin) :
-        # strip
-        alias = alias[:(len(alias) - len(origin) - 1)]
-    else:
-        raise ValueError("alias domain outside of origin: %s / %s" % (alias, origin))
-
-    return pvl.dns.zone.ZoneRecord.CNAME(alias, host)
-
-def process_hosts_names (options, hosts, origin) :
-    """
-        Yield ZoneRecords for hosts within the given zone.
-    """
-
-    for host in hosts :
-        # determine label within zone
-        if not origin :
-            label = pvl.dns.join(host.name, host.domain)
-        elif host.domain == origin :
-            label = host.name
-        elif host.domain and host.domain.endswith('.' + origin) :
-            fqdn = pvl.dns.join(host.name, host.domain)
-            label = fqdn[:(len(fqdn) - len(origin) - 1)]
-        elif host.domain :
-            log.debug("%s: domain out of zone: %s", host, origin)
-            continue
-        else :
-            log.debug("%s: fqdn out of zone: %s", host, origin)
-            continue
-        
-        if host.forward is None  :
-            pass
-        elif host.forward :
-            forward = pvl.dns.zone.fqdn(host.forward)
-
-            log.info("%s: forward: %s", host, forward)
-
-            yield pvl.dns.zone.ZoneRecord.CNAME(label, forward)
-            continue
-        else :
-            log.info("%s: skip forward", host)
-            continue
-
-        if host.ip :
-            yield pvl.dns.zone.ZoneRecord.A(label, host.ip)
-        
-        if host.alias4 :
-            yield pvl.dns.zone.ZoneRecord.A(host.ALIAS4_FMT.format(host=label), host.ip)
-
-        if host.ip6 :
-            yield pvl.dns.zone.ZoneRecord.AAAA(label, host.ip6)
-
-        if host.alias6 :
-            yield pvl.dns.zone.ZoneRecord.AAAA(label.ALIAS6_FMT.format(host=host), host.ip6)
-
-        if host.location and host.location_domain:
-            yield process_hosts_alias(options, origin, host.location_domain, host.location, label)
-        elif host.location:
-            yield process_hosts_alias(options, origin, host.domain, host.location, label)
-
-        for alias in host.alias :
-            yield process_hosts_alias(options, origin, host.domain, alias, label)
-
-        for alias4 in host.alias4 :
-            yield process_hosts_alias(options, origin, host.domain, alias4, host.ALIAS4_FMT.format(host=label))
-
-        for alias6 in host.alias6 :
-            yield process_hosts_alias(options, origin, host.domain, alias6, host.ALIAS6_FMT.format(host=label))
-
-def process_hosts_forward (options, hosts, origin) :
-    """
-        Generate DNS ZoneRecords for for hosts within the given zone origin.
-    """
-
-    if options.add_origin :
-        yield pvl.dns.zone.ZoneDirective.build(None, 'ORIGIN', origin)
-
-    by_name = dict()
-    by_name_type = dict()
-    
-    # list of types thare are allowed to be present for a host
-    MULTI_TYPES = ('A', 'AAAA')
-
-    for rr in process_hosts_names(options, hosts, origin) :
-        if (rr.name, rr.type) in by_name_type :
-            raise ValueError("%s: duplicate name/type: %s: %s" % (rr.name, rr, by_name_type[(rr.name, rr.type)]))
-        elif rr.type in MULTI_TYPES :
-            by_name_type[(rr.name, rr.type)] = rr
-        elif rr.name in by_name :
-            raise ValueError("%s: duplicate name: %s: %s" % (rr.name, rr, by_name[rr.name]))
-        
-        # always check these
-        by_name[rr.name] = rr
-        
-        # preserve ordering
-        yield rr
-
-def split_ipv6_parts (prefix) :
-    for hextet in prefix.rstrip(':').split(':') :
-        for nibble in hextet.rjust(4, '0') :
-            yield nibble
-
-def build_ipv6_parts (parts) :
-    for i in xrange(0, len(parts), 4) :
-        yield ''.join(parts[i:i+4])
-    
-    # suffix ::
-    if len(parts) < 32 :
-        yield ''
-        yield ''
-
-def parse_prefix (prefix) :
-    """
-        Return an ipaddr.IPNetwork from given IPv4/IPv6 prefix.
-
-        >>> parse_prefix('127.0.0.0/8')
-        IPv4Network('127.0.0.0/8')
-        >>> parse_prefix('192.0.2.128/26')
-        IPv4Network('192.0.2.128/26')
-        >>> parse_prefix('192.0.2.128-26')
-        IPv4Network('192.0.2.128-26')
-        >>> parse_prefix('127.')
-        IPv4Network('127.0.0.0/8')
-        >>> parse_prefix('10')
-        IPv4Network('10.0.0.0/8')
-        >>> parse_prefix('192.168')
-        IPv4Network('192.168.0.0/16')
-        >>> parse_prefix('fe80:')
-        IPv6Network('fe80::/16')
-        >>> parse_prefix('2001:db8::')
-        IPv6Network('2001:db8::/32')
-        >>> parse_prefix('2001:db8:1:2')
-        IPv6Network('2001:db8:1:2::/64')
-    """
-
-    if '/' in prefix :
-        return ipaddr.IPNetwork(prefix)
-    
-    elif '-' in prefix :
-        return ipaddr.IPNetwork(prefix.replace('-', '/'))
-
-    elif '.' in prefix or prefix.isdigit() :
-        parts = prefix.rstrip('.').split('.')
-        prefixlen = len(parts) * 8
-        
-        return ipaddr.IPv4Network('{prefix}/{prefixlen}'.format(
-            prefix      = '.'.join(parts + ['0' for i in xrange(4 - len(parts))]),
-            prefixlen   = prefixlen,
-        ))
-   
-    elif ':' in prefix :
-        parts = list(split_ipv6_parts(prefix))
-        prefixlen = len(parts) * 4
-
-        return ipaddr.IPv6Network('{prefix}/{prefixlen}'.format(
-            prefix      = ':'.join(build_ipv6_parts(parts)),
-            prefixlen   = prefixlen,
-        ))
-
-    else :
-        raise ValueError("Unrecognized IP prefix string: %s" % (prefix, ))
-
-def process_hosts_ips (options, hosts, prefix) :
-    """
-        Yield (ip, fqnd) for hosts within given prefix.
-    """
-
-    for host in hosts :
-        if prefix.version == 4 :
-            ip = host.ip
-        elif prefix.version == 6 :
-            ip = host.ip6
-        else :
-            raise ValueError("%s: unknown ip version: %s" % (prefix, prefix.version))
-
-        if not ip :
-            log.debug("%s: no ip%d", host, prefix.version)
-            continue
-
-        if ip not in prefix :
-            log.debug("%s: %s out of prefix: %s", host, ip, prefix)
-            continue
-        
-        label = pvl.dns.zone.reverse_label(prefix, ip)
-       
-        if host.reverse is None :
-            fqdn = host.fqdn()
-
-            log.info("%s %s[%s]: PTR %s", host, prefix, ip, fqdn)
-
-            yield host, ip, pvl.dns.zone.ZoneRecord.PTR(label, fqdn)
-
-        elif host.reverse :
-            alias = pvl.dns.zone.fqdn(host.reverse)
-            
-            log.info("%s %s[%s]: CNAME %s", host, prefix, ip, alias)
-
-            yield host, ip, pvl.dns.zone.ZoneRecord.CNAME(label, alias)
-
-        else :
-            log.info("%s %s[%s]: omit", host, prefix, ip)
-            continue
-
- 
-def process_hosts_reverse (options, hosts, prefix) :
-    """
-        Generate DNS ZoneRecords within the given prefix's reverse-dns zone for hosts.
-    """
-    
-    # collect data for records
-    by_ip = dict()
-    for host, ip, rr in process_hosts_ips(options, hosts, prefix) :
-        if ip in by_ip :
-            raise ValueError("%s: duplicate ip: %s: %s" % (host, ip, by_ip[ip]))
-        else :
-            by_ip[ip] = rr
-
-    if options.unknown_host :
-        # enumerate all of them
-        iter_ips = prefix.iterhosts()
-    else :
-        iter_ips = sorted(by_ip)
-
-    for ip in iter_ips :
-        if ip in by_ip :
-            yield by_ip[ip]
-        elif options.unknown_host :
-            label = pvl.dns.zone.reverse_label(prefix, ip)
-            fqdn = pvl.dns.zone.fqdn(options.unknown_host, options.hosts_domain)
-
-            log.info("%s %s[%s]: unused PTR %s", options.unknown_host, ip, prefix, fqdn)
-
-            yield pvl.dns.zone.ZoneRecord.PTR(label, fqdn)
-        else :
-            continue
-
-def apply_zone (options, zone) :
-    """
-        Output given ZoneRecord's
-    """
-
-    for record in zone :
-        print unicode(record)
-
-def main (argv) :
-    """
-        Generate bind zonefiles from host definitions.
-    """
-
-    parser = optparse.OptionParser(main.__doc__)
-    parser.add_option_group(pvl.args.parser(parser))
-    parser.add_option_group(pvl.hosts.optparser(parser))
-
-    parser.add_option('--add-origin',           action='store_true',
-            help="Include $ORIGIN directive in zone")
-
-    parser.add_option('--forward-zone',         metavar='DOMAIN',
-            help="Generate forward zone for domain")
-
-    parser.add_option('--reverse-zone',         metavar='PREFIX',
-            help="Generate reverse zone for prefix")
-
-    parser.add_option('--unknown-host',         metavar='NAME',
-            help="Generate records for unused IPs")
-
-    options, args = parser.parse_args(argv[1:])
-    pvl.args.apply(options)
-
-    # input
-    hosts = pvl.hosts.apply(options, args)
-
-    # process
-    if options.forward_zone :
-        apply_zone(options, 
-                process_hosts_forward(options, hosts, options.forward_zone),
-        )
-    
-    if options.reverse_zone :
-        apply_zone(options, 
-                process_hosts_reverse(options, hosts, parse_prefix(options.reverse_zone)),
-        )
-
-if __name__ == '__main__':
-    pvl.args.main(main)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/pvl.hosts-forward	Tue Feb 24 21:38:12 2015 +0200
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+import pvl.hosts.zone
+import sys
+
+sys.exit(pvl.hosts.zone.forward_main())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/pvl.hosts-reverse	Tue Feb 24 21:38:12 2015 +0200
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+import pvl.hosts.zone
+import sys
+
+sys.exit(pvl.hosts.zone.reverse_main())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/hosts/zone.py	Tue Feb 24 21:38:12 2015 +0200
@@ -0,0 +1,251 @@
+"""
+    Generate zonefile records from hosts
+"""
+
+import ipaddr
+import logging; log = logging.getLogger('pvl.hosts.zone')
+import pvl.dns
+
+class HostZoneError(Exception):
+    pass
+
+def resolve (origin, domain, name) :
+    """
+        Resolve relative CNAME for label@origin -> alias@domain
+    """
+
+    fqdn = pvl.dns.join(name, domain)
+
+    if not origin:
+        return fqdn
+
+    elif domain == origin:
+         return name
+
+    elif fqdn.endswith('.' + origin):
+        return pvl.dns.relative(origin, fqdn)
+
+    elif domain:
+        raise HostZoneError("{name}: domain {domain} out of zone {origin}".format(name=name, domain=domain, origin=origin))
+
+    else:
+        raise HostZoneError("{name}: fqdn {fqdn} out of zone {origin}".format(name=name, fqdn=fqdn, origin=origin))
+
+def host_forward (host, origin) :
+    """
+        Yield ZoneRecords for hosts within the given zone origin
+    """
+
+    try:
+        label = resolve(origin, host.domain, host.name)
+    except HostZoneError as error:
+        log.info("%s: skip: %s", host, error)
+        return
+
+    if host.forward:
+        forward = pvl.dns.zone.fqdn(host.forward)
+
+        log.info("%s: forward: %s", host, forward)
+
+        yield pvl.dns.ZoneRecord.CNAME(label, forward)
+        return
+
+    elif host.forward is not None:
+        log.info("%s: skip forward", host)
+        return
+    
+    # forward
+    if host.ip :
+        yield pvl.dns.ZoneRecord.A(label, host.ip)
+
+    if host.ip6 :
+        yield pvl.dns.ZoneRecord.AAAA(label, host.ip6)
+
+    if host.location:
+        location_alias, location_domain = host.location
+
+        if not location_domain:
+            location_domain = host.domain
+
+        yield pvl.dns.ZoneRecord.CNAME(resolve(origin, location_domain, location_alias), label)
+
+    for alias in host.alias4:
+        yield pvl.dns.ZoneRecord.CNAME(resolve(origin, host.domain, alias), label)
+
+    for alias in host.alias4:
+        yield pvl.dns.ZoneRecord.A(resolve(origin, host.domain, alias), host.ip)
+
+    for alias in host.alias6:
+         yield pvl.dns.ZoneRecord.AAAA(resolve(origin, host.domain, alias), host.ip6)
+
+def host_reverse (host, prefix) :
+    """
+        Yield (ip, fqnd) for host within given prefix.
+    """
+
+    if prefix.version == 4 :
+        ip = host.ip
+    elif prefix.version == 6 :
+        ip = host.ip6
+    else :
+        raise ValueError("%s: unknown ip version: %s" % (prefix, prefix.version))
+
+    if not ip :
+        log.debug("%s: no ip%d", host, prefix.version)
+        return
+
+    if ip not in prefix :
+        log.debug("%s: %s out of prefix: %s", host, ip, prefix)
+        return
+    
+    # relative label
+    label = pvl.dns.reverse_label(prefix, ip)
+   
+    if host.reverse :
+        alias = pvl.dns.zone.fqdn(host.reverse)
+        
+        log.info("%s %s[%s]: CNAME %s", host, prefix, ip, alias)
+
+        yield ip, pvl.dns.zone.ZoneRecord.CNAME(label, alias)
+
+    elif host.reverse is None :
+        fqdn = host.fqdn()
+
+        log.info("%s %s[%s]: PTR %s", host, prefix, ip, fqdn)
+
+        yield ip, pvl.dns.zone.ZoneRecord.PTR(label, fqdn)
+
+    else :
+        log.info("%s %s[%s]: omit", host, prefix, ip)
+ 
+def apply_hosts_forward (options, hosts, origin) :
+    """
+        Generate DNS ZoneRecords for for hosts within the given zone origin.
+
+        Yields ZoneRecords in Host-order
+    """
+
+    if options.add_origin :
+        yield pvl.dns.ZoneDirective.build(None, 'ORIGIN', origin)
+
+    by_name = dict()
+    by_cname = dict()
+    
+    for host in hosts:
+        if not host.domain:
+            log.debug("%s: skip without domain", host)
+
+        for rr in host_forward(host, origin) :
+            if rr.name in by_cname:
+                raise HostZoneError(host, "{host}: CNAME {cname} conflict: {rr}".format(host=host, cname=by_cname[rr.name].name, rr=rr))
+            elif rr.type == 'CNAME' and rr.name in by_name:
+                raise HostZoneError(host, "{host}: CNAME {cname} conflict: {rr}".format(host=host, cname=rr.name, rr=by_name[rr.name]))
+            
+            by_name[rr.name] = rr
+
+            if rr.type == 'CNAME':
+                by_cname[rr.name] = rr
+            
+            # preserve ordering
+            yield rr
+
+def apply_hosts_reverse (options, hosts, prefix) :
+    """
+        Generate DNS ZoneRecords within the given prefix's reverse-dns zone for hosts.
+
+        Yields ZoneRecords in IPAddress-order
+    """
+    
+    # collect data for records
+    by_ip = dict()
+
+    for host in hosts:
+        for ip, rr in host_reverse(host, prefix) :
+            if ip in by_ip :
+                raise HostZoneError(host, "{host}: IP {ip} conflict: {other}".format(host=host, ip=ip, other=by_ip[ip]))
+            
+            # do not retain order
+            by_ip[ip] = rr
+
+    if options.unknown_host :
+        # enumerate all of them
+        iter_ips = prefix.iterhosts()
+    else :
+        iter_ips = sorted(by_ip)
+
+    for ip in iter_ips :
+        if ip in by_ip :
+            yield by_ip[ip]
+
+        elif options.unknown_host:
+            # synthesize a record
+            label = pvl.dns.reverse_label(prefix, ip)
+            fqdn = pvl.dns.zone.fqdn(options.unknown_host, options.hosts_domain)
+
+            log.info("%s %s[%s]: unused PTR %s", options.unknown_host, ip, prefix, fqdn)
+
+            yield pvl.dns.zone.ZoneRecord.PTR(label, fqdn)
+
+        else :
+            continue
+
+import pvl.args
+import pvl.hosts.config
+
+import optparse 
+
+def forward_main () :
+    """
+        Generate bind zonefiles from host definitions.
+    """
+
+    parser = optparse.OptionParser(forward_main.__doc__)
+    parser.add_option_group(pvl.args.parser(parser))
+    parser.add_option_group(pvl.hosts.config.optparser(parser))
+
+    parser.add_option('--add-origin',           action='store_true',
+            help="Include $ORIGIN directive in zone")
+
+    parser.add_option('--forward-zone',         metavar='DOMAIN',
+            help="Generate forward zone for domain")
+
+    # input
+    options, args = parser.parse_args()
+    
+    pvl.args.apply(options)
+
+    hosts = pvl.hosts.apply(options, args)
+
+    # process
+    for rr in apply_hosts_forward(options, hosts, options.forward_zone):
+        print unicode(rr)
+    
+    return 0
+
+def reverse_main () :
+    """
+        Generate bind zonefiles from host definitions.
+    """
+
+    parser = optparse.OptionParser(reverse_main.__doc__)
+    parser.add_option_group(pvl.args.parser(parser))
+    parser.add_option_group(pvl.hosts.config.optparser(parser))
+
+    parser.add_option('--reverse-zone',         metavar='PREFIX',
+            help="Generate reverse zone for prefix")
+
+    parser.add_option('--unknown-host',         metavar='NAME',
+            help="Generate records for unused IPs")
+
+    # input
+    options, args = parser.parse_args()
+    
+    pvl.args.apply(options)
+
+    hosts = pvl.hosts.apply(options, args)
+
+    # process
+    for rr in apply_hosts_reverse(options, hosts, pvl.dns.parse_prefix(options.reverse_zone)):
+        print unicode(rr)
+
+    return 0