--- /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