pvl.hosts.config: fix handling of mixed boot=... boot.foo=... independent of dict ordering
#!/usr/bin/env python
"""
Generate bind zonefiles from a given input zonefile.
Takes a zonefile as input, and gives a new zonefile as output.
"""
import ipaddr
import logging; log = logging.getLogger('pvl.dns-generate')
import optparse
import pvl.args
import pvl.dns.reverse
import pvl.dns.process
def check_zone (rrs, whitelist_names=set(), whitelist_types=set()):
"""
Parse host/IP pairs from the zone, and verify that they are unique.
As an exception, names listed in the given whitelist may have multiple IPs.
"""
by_name = {}
by_ip = {}
check = True
for rr in rrs:
name = (rr.origin, rr.name)
# name
if name not in by_name:
pass
elif rr.type in whitelist_types:
log.debug("%s: Whitelist type duplicate: %s", rr, by_name[name])
elif rr.name in whitelist_names:
log.debug("%s: Whitelist name duplicate: %s", rr, by_name[name])
else:
log.warn("%s: Duplicate name: %s <-> %s", rr.line, rr, by_name[name])
check = False
by_name[name] = rr
# ip
if rr.type in ('A', 'AAAA'):
ip, = rr.data
if ip in by_ip:
log.warn("%s: Duplicate IP: %s <-> %s", rr.line, rr, by_ip[ip])
check = False
by_ip[ip] = rr
return check
def process_zone_reverse (rrs, prefix):
"""
Process zone data -> reverse zone data.
"""
for r in rrs:
if r.type == 'A':
ip, = r.data
ip = ipaddr.IPv4Address(ip)
elif r.type == 'AAAA':
ip, = r.data
ip = ipaddr.IPv6Address(ip)
else:
continue
if ip not in prefix:
log.debug("%s: skip: %s not in %s", rr, ip, prefix)
continue
ptr = pvl.dns.reverse_label(prefix, ip)
fqdn = pvl.dns.fqdn(r.name, r.origin)
yield pvl.dns.ZoneRecord.PTR(ptr, fqdn)
def main (argv):
parser = optparse.OptionParser(main.__doc__)
parser.add_option_group(pvl.args.parser(parser))
parser.add_option_group(pvl.dns.process.optparser(parser))
parser.add_option('--zone-origin', metavar='DOMAIN',
help="Domain to use for hosts in zone")
# check stage
parser.add_option('--check-hosts', action='store_true',
help="Check that host/IPs are unique. Use --quiet to silence warnings, and test exit status")
parser.add_option('--check-exempt', metavar='HOST', action='append',
help="Allow given names to have multiple records")
# reverse stage
parser.add_option('--reverse-prefix', metavar='NET',
help="Generate forward zone for given subnet (192.0.2, 2001:db8)")
parser.set_defaults(
check_exempt = [
'@'
],
)
# input
options, args = pvl.args.parse(parser, argv)
if options.reverse_prefix and not options.zone_origin:
log.error("--reverse-prefix requires --zone-origin")
zone = list(pvl.dns.process.apply_zone_records(options, options.zone_origin, args))
# check
if options.check_hosts:
whitelist_names = set(options.check_exempt)
log.info("Checking hosts: whitelist_names=%r", whitelist_names)
if not check_zone(zone, whitelist_names=whitelist_names):
log.error("Check zone failed, see warnings")
return 2
# transform
if options.reverse_prefix:
prefix = pvl.dns.reverse.parse_prefix(options.reverse_prefix)
zone = list(process_zone_reverse(zone, prefix))
else:
# pass through
pass
pvl.dns.process.apply_zone_output(options, zone)
return 0
if __name__ == '__main__':
pvl.args.main(main)