bin/pvl.dns-hosts
changeset 263 5ee3bcd5b4b5
parent 262 36c980a6d04a
child 264 ce29be46f705
equal deleted inserted replaced
262:36c980a6d04a 263:5ee3bcd5b4b5
     7 import pvl.args, optparse
     7 import pvl.args, optparse
     8 import pvl.dns.zone
     8 import pvl.dns.zone
     9 import pvl.dhcp.config
     9 import pvl.dhcp.config
    10 import pvl.ldap.args
    10 import pvl.ldap.args
    11 
    11 
       
    12 import ipaddr
       
    13 import optparse
    12 import collections
    14 import collections
    13 import re
    15 import re
    14 import logging; log = logging.getLogger('main')
    16 import logging; log = logging.getLogger('main')
    15 
    17 
    16 __version__ = '0.1'
    18 __version__ = '0.1'
    58 
    60 
    59     # output
    61     # output
    60     parser.add_option('--output-hosts',         metavar='FILE',
    62     parser.add_option('--output-hosts',         metavar='FILE',
    61             help="Output hosts file")
    63             help="Output hosts file")
    62 
    64 
       
    65     parser.add_option('--output-ip',            metavar='PREFIX',
       
    66             help="Output hosts by ip prefix")
       
    67 
    63     # defaults
    68     # defaults
    64     parser.set_defaults(
    69     parser.set_defaults(
    65 
    70 
    66     )
    71     )
    67     
    72     
    71     # apply
    76     # apply
    72     pvl.args.apply(options, argv[0])
    77     pvl.args.apply(options, argv[0])
    73 
    78 
    74     return options, args
    79     return options, args
    75 
    80 
    76 def process_zone_hosts (options, file) :
    81 def import_zone_hosts (options, file) :
    77     """
    82     """
    78         Yield host info from zonefile records.
    83         Yield host info from zonefile records.
    79     """
    84     """
    80 
    85 
    81     for rr in pvl.dns.zone.ZoneRecord.load(file) :
    86     for rr in pvl.dns.zone.ZoneRecord.load(file) :
    97             yield host, 'alias', rr.name
   102             yield host, 'alias', rr.name
    98 
   103 
    99         else :
   104         else :
   100             log.warn("%s: unknown rr: %s", rr.name, rr)
   105             log.warn("%s: unknown rr: %s", rr.name, rr)
   101 
   106 
   102 def process_dhcp_host (options, host, items) :
   107 def import_dhcp_host (options, host, items) :
   103     """
   108     """
   104         Yield host infos from a dhcp host ... { ... }
   109         Yield host infos from a dhcp host ... { ... }
   105     """
   110     """
   106 
   111 
   107     hostname = None
   112     hostname = None
   138         hostname = host
   143         hostname = host
   139 
   144 
   140     if hostname :
   145     if hostname :
   141         yield hostname, 'ethernet', ethernet
   146         yield hostname, 'ethernet', ethernet
   142 
   147 
   143 def process_dhcp_hosts (options, blocks) :
   148 def import_dhcp_hosts (options, blocks) :
   144     """
   149     """
   145         Process hosts from a parsed block
   150         Process hosts from a parsed block
   146     """
   151     """
   147 
   152 
   148     for block, items, blocks in blocks :
   153     for block, items, blocks in blocks :
   149         log.info("%s", block)
   154         log.info("%s", block)
   150         
   155         
   151         block, args = block[0], block[1:]
   156         block, args = block[0], block[1:]
   152 
   157 
   153         if block == 'group' :
   158         if block == 'group' :
   154             for info in process_dhcp_hosts(options, blocks) :
   159             for info in import_dhcp_hosts(options, blocks) :
   155                 yield info
   160                 yield info
   156         elif block == 'host' :
   161         elif block == 'host' :
   157             host, = args
   162             host, = args
   158 
   163 
   159             try :
   164             try :
   160                 for info in process_dhcp_host(options, host, items) :
   165                 for info in import_dhcp_host(options, host, items) :
   161                     yield info
   166                     yield info
   162             except ValueError as error :
   167             except ValueError as error :
   163                 log.warn("%s: invalid host: %s", host, error)
   168                 log.warn("%s: invalid host: %s", host, error)
   164         else:
   169         else:
   165             log.warn("ignore unknown block: %s", block)
   170             log.warn("ignore unknown block: %s", block)
   166 
   171 
   167 def process_dhcp_conf (options, file) :
   172 def import_dhcp_conf (options, file) :
   168     items, blocks = pvl.dhcp.config.DHCPConfigParser().load(file)
   173     items, blocks = pvl.dhcp.config.DHCPConfigParser().load(file)
   169 
   174 
   170     for item in items :
   175     for item in items :
   171         item, args = item[0], item[1:]
   176         item, args = item[0], item[1:]
   172 
   177 
   173         if item == 'include' :
   178         if item == 'include' :
   174             include, = args
   179             include, = args
   175             for info in process_dhcp_conf(options, pvl.args.apply_file(include)) :
   180             for info in import_dhcp_conf(options, pvl.args.apply_file(include)) :
   176                 yield info
   181                 yield info
   177         else :
   182         else :
   178             log.warn("ignore unknown item: %s", item)
   183             log.warn("ignore unknown item: %s", item)
   179     
   184     
   180     for info in process_dhcp_hosts(options, blocks) :
   185     for info in import_dhcp_hosts(options, blocks) :
   181         yield info
   186         yield info
   182 
       
   183 def apply_hosts_import (options) :
       
   184     """
       
   185         Import host infos from given files.
       
   186     """
       
   187 
       
   188     if options.import_zone_hosts:
       
   189         for info in process_zone_hosts(options,
       
   190                 pvl.args.apply_file(options.import_zone_hosts)) :
       
   191             yield info
       
   192     
       
   193     if options.import_dhcp_hosts:
       
   194         for info in process_dhcp_conf(options,
       
   195                 pvl.args.apply_file(options.import_dhcp_hosts)) :
       
   196             yield info
       
   197 
   187 
   198 ZONE_COMMENTS = (
   188 ZONE_COMMENTS = (
   199         re.compile(r'(?P<owner>[^/]+)\s*-\s+(?P<host>.+)'),
   189         re.compile(r'(?P<owner>[^/]+)\s*-\s+(?P<host>.+)'),
   200         re.compile(r'(?P<group>.+?)\s*/\s*(?P<owner>.+)\s+[/-]\s+(?P<host>.+)'),
   190         re.compile(r'(?P<group>.+?)\s*/\s*(?P<owner>.+)\s+[/-]\s+(?P<host>.+)'),
   201         re.compile(r'(?P<group>.+?)\s*/\s*(?P<owner>.+)\s+[(]\s*(?P<host>.+)[)]'),
   191         re.compile(r'(?P<group>.+?)\s*/\s*(?P<owner>.+)\s+[(]\s*(?P<host>.+)[)]'),
   237 
   227 
   238     for field, value in matches.iteritems() :
   228     for field, value in matches.iteritems() :
   239         if value :
   229         if value :
   240             yield field, value.strip()
   230             yield field, value.strip()
   241 
   231 
   242 HOST_OWNERS = {
   232 NONE_OWNERS = set((
   243     u'tech':        'root',
   233     u'tech',
   244     u'atk':         'root',
   234     u'atk',
   245     u'toimisto':    'root',
   235     u'toimisto',
   246 }
   236 ))
   247 
   237 
   248 def process_host_owner (options, host, info) :
   238 def process_host_owner_ldap (options, host, info) :
   249     """
   239     """
   250         Yield guesses for user from LDAP.
   240         Yield guesses for user from LDAP.
   251     """
   241     """
   252 
       
   253     if info.get('owner').lower() in HOST_OWNERS :
       
   254         yield HOST_OWNERS[info.get('owner').lower()]
       
   255 
   242 
   256     if info.get('mail') :
   243     if info.get('mail') :
   257         for user in options.ldap.users.filter(
   244         for user in options.ldap.users.filter(
   258                 { 'mailLocalAddress': info['mail'] },
   245                 { 'mailLocalAddress': info['mail'] },
   259                 { 'uid': info['mail'] },
   246                 { 'uid': info['mail'] },
   260         ) :
   247         ) :
   261             yield user['uid']
   248             yield user, None
   262 
   249 
   263     if info.get('group') and info.get('owner') :
   250     if info.get('group') and info.get('owner') :
   264         groups = options.ldap.groups.filter(cn=info['group'])
   251         groups = options.ldap.groups.filter(cn=info['group'])
   265 
   252 
   266         for group in groups :
   253         for group in groups :
   267             for user in options.ldap.users.filter({
   254             for user in options.ldap.users.filter({
   268                 'gidNumber': group['gidNumber'],
   255                 'gidNumber': group['gidNumber'],
   269                 'cn': info['owner'],
   256                 'cn': info['owner'],
   270             }) :
   257             }) :
   271                 yield user['uid']
   258                 yield user, group
   272 
   259 
   273     if info.get('owner') :
   260     if info.get('owner') :
   274             for user in options.ldap.users.filter({
   261             for user in options.ldap.users.filter({
   275                 'cn': info['owner'],
   262                 'cn': info['owner'],
   276             }) :
   263             }) :
   277                 yield user['uid']
   264                 yield user, None
       
   265 
       
   266 def process_host_owner (options, host, info) :
       
   267     """
       
   268         Return (owner, comment) for host based on info, or None.
       
   269     """
       
   270 
       
   271     if info.get('owner').lower() in NONE_OWNERS :
       
   272         return
       
   273     
       
   274     # from ldap?
       
   275     for ldap in process_host_owner_ldap(options, host, info) :
       
   276         user, group = ldap
       
   277         
       
   278         if not group :
       
   279             # get group from ldap
       
   280             group = options.ldap.users.group(user)
       
   281         
       
   282         return user['uid'], u"{group} / {user}".format(
       
   283                 user    = user.getunicode('cn'),
       
   284                 group   = group.getunicode('cn'),
       
   285         )
   278 
   286 
   279 def process_host_comments (options, host, info) :
   287 def process_host_comments (options, host, info) :
   280     """
   288     """
   281         Process host fields from comment.
   289         Process host fields from comment.
   282 
   290 
   283         Attempts to find owner from LDAP..
   291         Attempts to find owner from LDAP..
   284     """
   292     """
   285 
   293 
   286     log.debug("%s: %s", host, info)
   294     log.debug("%s: %s", host, info)
   287     
   295     
   288     for owner in process_host_owner(options, host, info) :
   296     owner = process_host_owner(options, host, info) 
   289         log.info("%s: %s", host, owner)
   297 
   290 
   298     if owner :
       
   299         owner, comment = owner
       
   300         
       
   301         log.info("%s: %s (%s)", host, owner, comment)
       
   302         
       
   303         yield 'owner-comment', comment
   291         yield 'owner', owner,
   304         yield 'owner', owner,
   292 
       
   293         # only use the first match
       
   294         break
       
   295     else :
   305     else :
   296         log.warn("%s: no owner: %s", host, info)
   306         log.warn("%s: no owner: %s", host, info)
   297     
   307     
   298     if info.get('host') :
   308     if info.get('host') :
   299         yield 'comment', info['host']
   309         yield 'comment', info['host']
   321             ).encode('utf-8')
   331             ).encode('utf-8')
   322         
   332         
   323 
   333 
   324         for field, value in process_host_comments(options, host, fields) :
   334         for field, value in process_host_comments(options, host, fields) :
   325             yield host, field, value
   335             yield host, field, value
   326             
   336 
   327 def process_hosts_import (options, import_hosts) :
   337 def apply_hosts_import (options) :
   328     """
   338     """
   329         Import host definitions from given infos
   339         Import host infos from given files.
   330     """
   340     """
   331 
   341 
       
   342     if options.import_zone_hosts:
       
   343         for info in import_zone_hosts(options,
       
   344                 pvl.args.apply_file(options.import_zone_hosts)) :
       
   345             yield info
       
   346     
       
   347     if options.import_dhcp_hosts:
       
   348         for info in import_dhcp_conf(options,
       
   349                 pvl.args.apply_file(options.import_dhcp_hosts)) :
       
   350             yield info
       
   351        
       
   352 def import_hosts (options) :
       
   353     """
       
   354         Import hosts from dns/dhcp.
       
   355     """
       
   356 
       
   357     import_hosts = apply_hosts_import(options)
       
   358     import_hosts = process_hosts_comments(options, import_hosts)
       
   359     
       
   360     # gather
   332     hosts = collections.defaultdict(lambda: collections.defaultdict(list))
   361     hosts = collections.defaultdict(lambda: collections.defaultdict(list))
   333 
   362 
   334     for host, field, value in import_hosts :
   363     for host, field, value in import_hosts :
   335         hosts[host][field].append(value)
   364         hosts[host][field].append(value)
   336     
   365     
   337     return hosts.iteritems()
   366     return hosts.iteritems()
   338 
   367 
       
   368 def select_hosts_ip (options, hosts, network) :
       
   369     for host, fields in hosts :
       
   370         ip = fields.get('ip')
       
   371 
       
   372         if not ip :
       
   373             continue
       
   374 
       
   375         ip = ipaddr.IPAddress(ip[0])
       
   376 
       
   377         if ip in network :
       
   378             yield ip, host, fields
       
   379 
       
   380 
       
   381 def export_hosts (options, hosts) :
       
   382     """
       
   383         Export hosts to file.
       
   384     """
       
   385 
       
   386     file = pvl.args.apply_file(options.output_hosts, 'w', options.output_charset)
       
   387 
       
   388     if options.output_ip :
       
   389         prefix = ipaddr.IPNetwork(options.output_ip)
       
   390     
       
   391         # filter + sort
       
   392         hosts = [(host, fields) for ip, host, fields in sorted(select_hosts_ip(options, hosts, prefix))]
       
   393 
       
   394     for host, fields in hosts :
       
   395         for comment in fields.get('comment', ()) :
       
   396             print >>file, u"# {comment}".format(comment=comment)
       
   397 
       
   398         print >>file, u"[{host}]".format(host=host)
       
   399         
       
   400         for field, fmt in (
       
   401                 ('ip',              None),
       
   402                 ('ethernet',        None),
       
   403                 ('owner',           u"\t{field:15} = {value} # {fields[owner-comment][0]}"),
       
   404         ) :
       
   405             if not fmt :
       
   406                 fmt = u"\t{field:15} = {value}"
       
   407 
       
   408             for value in fields.get(field, ()) :
       
   409                 print >>file, fmt.format(field=field, value=value, fields=fields)
       
   410         
       
   411         print >>file
   339 
   412 
   340 def main (argv) :
   413 def main (argv) :
   341     options, args = parse_options(argv)
   414     options, args = parse_options(argv)
   342 
   415 
   343     options.ldap = pvl.ldap.args.apply(options)
   416     options.ldap = pvl.ldap.args.apply(options)
   345     if args :
   418     if args :
   346         # direct from file
   419         # direct from file
   347         hosts = pvl.args.apply_files(args, 'r', options.input_charset)
   420         hosts = pvl.args.apply_files(args, 'r', options.input_charset)
   348     else :
   421     else :
   349         # import
   422         # import
   350         import_hosts = apply_hosts_import(options)
   423         hosts = import_hosts(options)
   351         import_hosts = process_hosts_comments(options, import_hosts)
       
   352         hosts = process_hosts_import(options, import_hosts)
       
   353    
   424    
   354     # output
   425     # output
   355     if options.output_hosts :
   426     if options.output_hosts :
   356         for host, fields in hosts :
   427         export_hosts(options, hosts)
   357             print host
       
   358 
       
   359             for field, values in fields.iteritems() :
       
   360                 for value in values :
       
   361                     print "\t", field, "\t", value.encode(options.output_charset)
       
   362 
       
   363     return 0
       
   364 
   428 
   365 if __name__ == '__main__':
   429 if __name__ == '__main__':
   366     pvl.args.main(main)
   430     pvl.args.main(main)