pvl/dns/zone.py
changeset 640 620d5a3beec4
parent 639 cc27c830a911
child 642 c25834508569
equal deleted inserted replaced
639:cc27c830a911 640:620d5a3beec4
    52 
    52 
    53             Tracks $ORIGIN and $TTL state for ZoneRecords/ZoneDirectives.
    53             Tracks $ORIGIN and $TTL state for ZoneRecords/ZoneDirectives.
    54         """
    54         """
    55 
    55 
    56         for line in cls.parse(file) :
    56         for line in cls.parse(file) :
    57             if line.parts[0].startswith('$'):
    57             if not line.parts:
       
    58                 log.debug("%s: Skip empty line: %r", line, line)
       
    59                 continue
       
    60 
       
    61             elif line.parts[0].startswith('$'):
    58                 # control directive
    62                 # control directive
    59                 line_type = ZoneDirective
    63                 line_type = ZoneDirective
    60 
    64 
    61             else :
    65             else :
    62                 # normal record
    66                 # normal record
   211             Build directive from optional parts
   215             Build directive from optional parts
   212         """
   216         """
   213 
   217 
   214         return cls(directive, arguments, **opts)
   218         return cls(directive, arguments, **opts)
   215 
   219 
       
   220     @classmethod
       
   221     def INCLUDE (cls, path, origin=None, **opts):
       
   222         """
       
   223             Build $INCLUDE "path" [origin]
       
   224 
       
   225             Note that origin should be a FQDN, or it will be interpreted relative to the current origin!
       
   226         """
       
   227 
       
   228         if origin:
       
   229             return cls.build('INCLUDE', zone_quote(path), origin, **opts)
       
   230         else:
       
   231             return cls.build('INCLUDE', zone_quote(path), **opts)
       
   232 
   216     def __init__ (self, directive, arguments, comment=None, line=None, origin=None):
   233     def __init__ (self, directive, arguments, comment=None, line=None, origin=None):
   217         """
   234         """
   218             directive       - uppercase directive name, withtout leading $
   235             directive       - uppercase directive name, withtout leading $
   219             arguments [str] - list of directive arguments
   236             arguments [str] - list of directive arguments
   220             comment         - optional trailing comment
   237             comment         - optional trailing comment
   312         """
   329         """
   313         
   330         
   314         name = None
   331         name = None
   315 
   332 
   316         for line in ZoneLine.parse(file):
   333         for line in ZoneLine.parse(file):
   317             if line.parts[0].startswith('$'):
   334             if not line.parts:
       
   335                 log.debug("%s: Skip empty line: %r", line, line)
       
   336                 continue
       
   337 
       
   338             elif line.parts[0].startswith('$'):
   318                 directive = ZoneDirective.parse(line.parts,
   339                 directive = ZoneDirective.parse(line.parts,
   319                         origin      = origin,
   340                         origin      = origin,
   320                         line        = line,
   341                         line        = line,
   321                         comment     = line.comment,
   342                         comment     = line.comment,
   322                 )
   343                 )
   324                 log.debug("%s: %s", line, directive)
   345                 log.debug("%s: %s", line, directive)
   325 
   346 
   326                 if directive.directive == 'ORIGIN':
   347                 if directive.directive == 'ORIGIN':
   327                     directive_origin, = directive.arguments
   348                     directive_origin, = directive.arguments
   328                     
   349                     
   329                     log.info("%s: $ORIGIN %s -> %s", file, origin, directive_origin)
   350                     log.info("%s: $ORIGIN %s <- %s", line, directive_origin, origin)
   330                     
   351                     
   331                     origin = pvl.dns.labels.join(origin, directive_origin)
   352                     origin = pvl.dns.labels.join(origin, directive_origin)
   332 
   353 
   333                 elif directive.directive == 'TTL' :
   354                 elif directive.directive == 'TTL' :
   334                     directive_ttl, = directive.arguments
   355                     directive_ttl, = directive.arguments
   335                     
   356                     
   336                     log.info("%s: $TTL %d -> %s", file, ttl, directive_ttl)
   357                     log.info("%s: $TTL %s <- %s", line, directive_ttl, ttl)
   337                     
   358                     
   338                     ttl = int(directive_ttl)
   359                     ttl = int(directive_ttl)
   339 
   360 
   340                 elif directive.directive == 'GENERATE' :
   361                 elif directive.directive == 'GENERATE' :
   341                     for record in process_generate(line, directive.arguments,
   362                     for record in process_generate(line, directive.arguments,
   396 
   417 
   397         # parse ttl/cls/type
   418         # parse ttl/cls/type
   398         _cls = None
   419         _cls = None
   399 
   420 
   400         if parts and parts[0][0].isdigit() :
   421         if parts and parts[0][0].isdigit() :
   401             ttl = int(parts.pop(0))
   422             ttl = parts.pop(0)
   402 
   423 
   403         if parts and parts[0].upper() in ('IN', 'CH') :
   424         if parts and parts[0].upper() in ('IN', 'CH') :
   404             _cls = parts.pop(0).upper()
   425             _cls = parts.pop(0)
   405 
   426 
   406         # always have type
   427         # always have type
   407         type = parts.pop(0).upper()
   428         type = parts.pop(0)
   408 
   429 
   409         # remaining parts are data
   430         # remaining parts are data
   410         data = parts
   431         data = parts
   411 
   432 
   412         log.debug("  ttl=%r, cls=%r, type=%r, data=%r", ttl, _cls, type, data)
   433         log.debug("  ttl=%r, cls=%r, type=%r, data=%r", ttl, _cls, type, data)
   413 
   434 
   414         return cls(name, ttl, _cls, type, data, line=line, **opts)
   435         # optional subclass for build()
       
   436         cls = ZONE_RECORD_TYPES.get(type.upper(), cls)
       
   437 
       
   438         return cls.build(name, type, *data, ttl=ttl, cls=_cls, line=line, **opts)
   415     
   439     
   416     @classmethod
   440     @classmethod
   417     def build (_cls, name, type, *data, **opts):
   441     def build (_cls, name, type, *data, **opts):
   418         """
   442         """
   419             Simple interface to build ZoneRecord from required parts. All optional fields must be given as keyword arguments.
   443             Simple interface to build ZoneRecord from required parts. All optional fields must be given as keyword arguments.
   534     def __repr__ (self) :
   558     def __repr__ (self) :
   535         return '%s(%s)' % (self.__class__.__name__, ', '.join(repr(arg) for arg in (
   559         return '%s(%s)' % (self.__class__.__name__, ', '.join(repr(arg) for arg in (
   536             self.name, self.ttl, self.cls, self.type, self.data
   560             self.name, self.ttl, self.cls, self.type, self.data
   537         )))
   561         )))
   538 
   562 
   539 class SOA (ZoneRecord) :
   563 class ZoneRecordSOA (ZoneRecord):
   540     @classmethod
   564     """
   541     def build (cls, line, name, ttl, _cls, type, data, **opts) :
   565         Specialized SOA record.
   542         assert name == '@'
   566     """
   543 
   567 
   544         return cls(*data,
   568     @classmethod
   545             ttl     = ttl,
   569     def build (_cls, name, type,
   546             cls     = cls,
   570             master, contact, serial, refresh, retry, expire, nxttl, 
   547             line    = line,
   571             line=None,
       
   572             **opts
       
   573     ):
       
   574         assert type == 'SOA'
       
   575 
       
   576         if name != '@':
       
   577             raise ZoneLineError(line, "SOA should be @: {name}", name=name)
       
   578                 
       
   579         return super(ZoneRecordSOA, _cls).build('@', 'SOA', 
       
   580             master, contact, serial, refresh, retry, expire, nxttl,
   548             **opts
   581             **opts
   549         )
   582         )
   550 
   583 
   551     def __init__ (self, master, contact, serial, refresh, retry, expire, nxttl, **opts) :
   584     def __init__ (self, name, ttl, cls, type, data, **opts):
   552         super(SOA, self).__init__('@', 'SOA',
   585         super(ZoneRecordSOA, self).__init__(name, ttl, cls, type, data, **opts)
   553             [master, contact, serial, refresh, retry, expire, nxttl],
   586 
   554             **opts
   587         self.master, self.contact, self.serial, self.refresh, self.retry, self.expire, self.nxttl = data
   555         )
   588 
   556 
   589 ZONE_RECORD_TYPES = {
   557         self.master = master
   590     'SOA':      ZoneRecordSOA,
   558         self.contact = contact
   591 }
   559         self.serial = serial
       
   560         self.refresh = refresh
       
   561         self.retry = retry
       
   562         self.expire = expire
       
   563         self.nxttl = nxttl
       
   564