pvl/hosts/zone.py
changeset 458 600ad9eb6f25
child 462 6d699c76d75d
equal deleted inserted replaced
457:1e925a1cc8de 458:600ad9eb6f25
       
     1 """
       
     2     Generate zonefile records from hosts
       
     3 """
       
     4 
       
     5 import ipaddr
       
     6 import logging; log = logging.getLogger('pvl.hosts.zone')
       
     7 import pvl.dns
       
     8 
       
     9 class HostZoneError(Exception):
       
    10     pass
       
    11 
       
    12 def resolve (origin, domain, name) :
       
    13     """
       
    14         Resolve relative CNAME for label@origin -> alias@domain
       
    15     """
       
    16 
       
    17     fqdn = pvl.dns.join(name, domain)
       
    18 
       
    19     if not origin:
       
    20         return fqdn
       
    21 
       
    22     elif domain == origin:
       
    23          return name
       
    24 
       
    25     elif fqdn.endswith('.' + origin):
       
    26         return pvl.dns.relative(origin, fqdn)
       
    27 
       
    28     elif domain:
       
    29         raise HostZoneError("{name}: domain {domain} out of zone {origin}".format(name=name, domain=domain, origin=origin))
       
    30 
       
    31     else:
       
    32         raise HostZoneError("{name}: fqdn {fqdn} out of zone {origin}".format(name=name, fqdn=fqdn, origin=origin))
       
    33 
       
    34 def host_forward (host, origin) :
       
    35     """
       
    36         Yield ZoneRecords for hosts within the given zone origin
       
    37     """
       
    38 
       
    39     try:
       
    40         label = resolve(origin, host.domain, host.name)
       
    41     except HostZoneError as error:
       
    42         log.info("%s: skip: %s", host, error)
       
    43         return
       
    44 
       
    45     if host.forward:
       
    46         forward = pvl.dns.zone.fqdn(host.forward)
       
    47 
       
    48         log.info("%s: forward: %s", host, forward)
       
    49 
       
    50         yield pvl.dns.ZoneRecord.CNAME(label, forward)
       
    51         return
       
    52 
       
    53     elif host.forward is not None:
       
    54         log.info("%s: skip forward", host)
       
    55         return
       
    56     
       
    57     # forward
       
    58     if host.ip :
       
    59         yield pvl.dns.ZoneRecord.A(label, host.ip)
       
    60 
       
    61     if host.ip6 :
       
    62         yield pvl.dns.ZoneRecord.AAAA(label, host.ip6)
       
    63 
       
    64     if host.location:
       
    65         location_alias, location_domain = host.location
       
    66 
       
    67         if not location_domain:
       
    68             location_domain = host.domain
       
    69 
       
    70         yield pvl.dns.ZoneRecord.CNAME(resolve(origin, location_domain, location_alias), label)
       
    71 
       
    72     for alias in host.alias4:
       
    73         yield pvl.dns.ZoneRecord.CNAME(resolve(origin, host.domain, alias), label)
       
    74 
       
    75     for alias in host.alias4:
       
    76         yield pvl.dns.ZoneRecord.A(resolve(origin, host.domain, alias), host.ip)
       
    77 
       
    78     for alias in host.alias6:
       
    79          yield pvl.dns.ZoneRecord.AAAA(resolve(origin, host.domain, alias), host.ip6)
       
    80 
       
    81 def host_reverse (host, prefix) :
       
    82     """
       
    83         Yield (ip, fqnd) for host within given prefix.
       
    84     """
       
    85 
       
    86     if prefix.version == 4 :
       
    87         ip = host.ip
       
    88     elif prefix.version == 6 :
       
    89         ip = host.ip6
       
    90     else :
       
    91         raise ValueError("%s: unknown ip version: %s" % (prefix, prefix.version))
       
    92 
       
    93     if not ip :
       
    94         log.debug("%s: no ip%d", host, prefix.version)
       
    95         return
       
    96 
       
    97     if ip not in prefix :
       
    98         log.debug("%s: %s out of prefix: %s", host, ip, prefix)
       
    99         return
       
   100     
       
   101     # relative label
       
   102     label = pvl.dns.reverse_label(prefix, ip)
       
   103    
       
   104     if host.reverse :
       
   105         alias = pvl.dns.zone.fqdn(host.reverse)
       
   106         
       
   107         log.info("%s %s[%s]: CNAME %s", host, prefix, ip, alias)
       
   108 
       
   109         yield ip, pvl.dns.zone.ZoneRecord.CNAME(label, alias)
       
   110 
       
   111     elif host.reverse is None :
       
   112         fqdn = host.fqdn()
       
   113 
       
   114         log.info("%s %s[%s]: PTR %s", host, prefix, ip, fqdn)
       
   115 
       
   116         yield ip, pvl.dns.zone.ZoneRecord.PTR(label, fqdn)
       
   117 
       
   118     else :
       
   119         log.info("%s %s[%s]: omit", host, prefix, ip)
       
   120  
       
   121 def apply_hosts_forward (options, hosts, origin) :
       
   122     """
       
   123         Generate DNS ZoneRecords for for hosts within the given zone origin.
       
   124 
       
   125         Yields ZoneRecords in Host-order
       
   126     """
       
   127 
       
   128     if options.add_origin :
       
   129         yield pvl.dns.ZoneDirective.build(None, 'ORIGIN', origin)
       
   130 
       
   131     by_name = dict()
       
   132     by_cname = dict()
       
   133     
       
   134     for host in hosts:
       
   135         if not host.domain:
       
   136             log.debug("%s: skip without domain", host)
       
   137 
       
   138         for rr in host_forward(host, origin) :
       
   139             if rr.name in by_cname:
       
   140                 raise HostZoneError(host, "{host}: CNAME {cname} conflict: {rr}".format(host=host, cname=by_cname[rr.name].name, rr=rr))
       
   141             elif rr.type == 'CNAME' and rr.name in by_name:
       
   142                 raise HostZoneError(host, "{host}: CNAME {cname} conflict: {rr}".format(host=host, cname=rr.name, rr=by_name[rr.name]))
       
   143             
       
   144             by_name[rr.name] = rr
       
   145 
       
   146             if rr.type == 'CNAME':
       
   147                 by_cname[rr.name] = rr
       
   148             
       
   149             # preserve ordering
       
   150             yield rr
       
   151 
       
   152 def apply_hosts_reverse (options, hosts, prefix) :
       
   153     """
       
   154         Generate DNS ZoneRecords within the given prefix's reverse-dns zone for hosts.
       
   155 
       
   156         Yields ZoneRecords in IPAddress-order
       
   157     """
       
   158     
       
   159     # collect data for records
       
   160     by_ip = dict()
       
   161 
       
   162     for host in hosts:
       
   163         for ip, rr in host_reverse(host, prefix) :
       
   164             if ip in by_ip :
       
   165                 raise HostZoneError(host, "{host}: IP {ip} conflict: {other}".format(host=host, ip=ip, other=by_ip[ip]))
       
   166             
       
   167             # do not retain order
       
   168             by_ip[ip] = rr
       
   169 
       
   170     if options.unknown_host :
       
   171         # enumerate all of them
       
   172         iter_ips = prefix.iterhosts()
       
   173     else :
       
   174         iter_ips = sorted(by_ip)
       
   175 
       
   176     for ip in iter_ips :
       
   177         if ip in by_ip :
       
   178             yield by_ip[ip]
       
   179 
       
   180         elif options.unknown_host:
       
   181             # synthesize a record
       
   182             label = pvl.dns.reverse_label(prefix, ip)
       
   183             fqdn = pvl.dns.zone.fqdn(options.unknown_host, options.hosts_domain)
       
   184 
       
   185             log.info("%s %s[%s]: unused PTR %s", options.unknown_host, ip, prefix, fqdn)
       
   186 
       
   187             yield pvl.dns.zone.ZoneRecord.PTR(label, fqdn)
       
   188 
       
   189         else :
       
   190             continue
       
   191 
       
   192 import pvl.args
       
   193 import pvl.hosts.config
       
   194 
       
   195 import optparse 
       
   196 
       
   197 def forward_main () :
       
   198     """
       
   199         Generate bind zonefiles from host definitions.
       
   200     """
       
   201 
       
   202     parser = optparse.OptionParser(forward_main.__doc__)
       
   203     parser.add_option_group(pvl.args.parser(parser))
       
   204     parser.add_option_group(pvl.hosts.config.optparser(parser))
       
   205 
       
   206     parser.add_option('--add-origin',           action='store_true',
       
   207             help="Include $ORIGIN directive in zone")
       
   208 
       
   209     parser.add_option('--forward-zone',         metavar='DOMAIN',
       
   210             help="Generate forward zone for domain")
       
   211 
       
   212     # input
       
   213     options, args = parser.parse_args()
       
   214     
       
   215     pvl.args.apply(options)
       
   216 
       
   217     hosts = pvl.hosts.apply(options, args)
       
   218 
       
   219     # process
       
   220     for rr in apply_hosts_forward(options, hosts, options.forward_zone):
       
   221         print unicode(rr)
       
   222     
       
   223     return 0
       
   224 
       
   225 def reverse_main () :
       
   226     """
       
   227         Generate bind zonefiles from host definitions.
       
   228     """
       
   229 
       
   230     parser = optparse.OptionParser(reverse_main.__doc__)
       
   231     parser.add_option_group(pvl.args.parser(parser))
       
   232     parser.add_option_group(pvl.hosts.config.optparser(parser))
       
   233 
       
   234     parser.add_option('--reverse-zone',         metavar='PREFIX',
       
   235             help="Generate reverse zone for prefix")
       
   236 
       
   237     parser.add_option('--unknown-host',         metavar='NAME',
       
   238             help="Generate records for unused IPs")
       
   239 
       
   240     # input
       
   241     options, args = parser.parse_args()
       
   242     
       
   243     pvl.args.apply(options)
       
   244 
       
   245     hosts = pvl.hosts.apply(options, args)
       
   246 
       
   247     # process
       
   248     for rr in apply_hosts_reverse(options, hosts, pvl.dns.parse_prefix(options.reverse_zone)):
       
   249         print unicode(rr)
       
   250 
       
   251     return 0