bin/pvl.dns-serial
changeset 234 472f0a422234
parent 233 c4941645464c
child 246 819320b0cf81
equal deleted inserted replaced
233:c4941645464c 234:472f0a422234
     3 
     3 
     4 """
     4 """
     5     Update zone serials.
     5     Update zone serials.
     6 """
     6 """
     7 
     7 
     8 __version__ = '0.0.1-dev'
     8 from pvl.dns import __version__
       
     9 import pvl.dns.serial
     9 
    10 
       
    11 import os.path
       
    12 
       
    13 import pvl.args
    10 import optparse
    14 import optparse
    11 import codecs
    15 import logging; log = logging.getLogger('main')
    12 import os.path
       
    13 from datetime import datetime
       
    14 import logging
       
    15 
       
    16 log = logging.getLogger('main')
       
    17 
       
    18 # command-line options, global state
       
    19 options = None
       
    20 
    16 
    21 def parse_options (argv) :
    17 def parse_options (argv) :
    22     """
    18     """
    23         Parse command-line arguments.
    19         Parse command-line arguments.
    24     """
    20     """
    25     
    21 
    26     prog = argv[0]
    22     prog = argv[0]
    27 
    23 
    28     parser = optparse.OptionParser(
    24     parser = optparse.OptionParser(
    29             prog        = prog,
    25             prog        = prog,
    30             usage       = '%prog: [options]',
    26             usage       = '%prog: [options] <serial-path> ...',
    31             version     = __version__,
    27             version     = __version__,
    32 
    28 
    33             # module docstring
    29             # module docstring
    34             description = __doc__,
    30             description = __doc__,
    35     )
    31     )
       
    32     
       
    33     # options
       
    34     parser.add_option_group(pvl.args.parser(parser))
    36 
    35 
    37     # logging
    36     parser.add_option('-n', '--noop',       action='store_true',
    38     general = optparse.OptionGroup(parser, "General Options")
    37             help="Do not actually update serial")
    39 
       
    40     general.add_option('-q', '--quiet',     dest='loglevel', action='store_const', const=logging.ERROR, help="Less output")
       
    41     general.add_option('-v', '--verbose',   dest='loglevel', action='store_const', const=logging.INFO,  help="More output")
       
    42     general.add_option('-D', '--debug',     dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output")
       
    43 
       
    44     parser.add_option_group(general)
       
    45 
    38 
    46     # defaults
    39     # defaults
    47     parser.set_defaults(
    40     parser.set_defaults(
    48         loglevel            = logging.WARN,
    41 
    49         expand              = [],
       
    50     )
    42     )
    51     
    43     
    52     # parse
    44     # parse
    53     options, args = parser.parse_args(argv[1:])
    45     options, args = parser.parse_args(argv[1:])
    54 
    46     
    55     # configure
    47     # apply
    56     logging.basicConfig(
    48     pvl.args.apply(options, prog)
    57         format  = prog + ': %(name)s: %(levelname)s %(funcName)s : %(message)s',
       
    58         level   = options.loglevel,
       
    59     )
       
    60 
    49 
    61     return options, args
    50     return options, args
    62 
    51 
    63 # date fmt to use in serial
    52 def process_serial (options, path) :
    64 DATE_FMT = '%Y%m%d'
       
    65 DATE_LEN = 8
       
    66 
       
    67 SERIAL_FMT = "{date:8}{count:02}"
       
    68 SERIAL_LEN = 10
       
    69 
       
    70 def format_serial (date, count) :
       
    71     return SERIAL_FMT.format(date=date.strftime(DATE_FMT), count=count)
       
    72 
       
    73 def next_count (value, date, count) :
       
    74     """
       
    75         Return serial with next count.
       
    76     """
       
    77 
       
    78     count += 1
       
    79 
       
    80     # check
       
    81     if count > 99 :
       
    82         serial = str(value + 1)
       
    83 
       
    84         log.warn("Serial count rollover: %s, %s; fallback -> %s", date, count, serial)
       
    85 
       
    86         return serial
       
    87 
       
    88     return format_serial(date, count)
       
    89 
       
    90 def update_serial (serial) :
       
    91     """
       
    92         Update new serial number into given file, based on date.
       
    93     """
       
    94     
       
    95     today = datetime.now().date()
       
    96 
       
    97     # handle
       
    98     if not serial :
       
    99         # fresh
       
   100         log.info("Setting initial serial: %s01", today)
       
   101         
       
   102         return format_serial(today, 1)
       
   103 
       
   104     elif len(serial) != SERIAL_LEN :
       
   105         log.warn("Invalid serial format: %s", serial)
       
   106         
       
   107         value = int(serial)
       
   108         serial = str(value + 1)
       
   109 
       
   110         log.info("Incrementing serial: %d -> %s", value, serial)
       
   111 
       
   112         return serial
       
   113 
       
   114     else :
       
   115         # parse
       
   116         value = int(serial)
       
   117 
       
   118         try :
       
   119             date = datetime.strptime(serial[:DATE_LEN], DATE_FMT).date()
       
   120             count = int(serial[DATE_LEN:])
       
   121 
       
   122         except ValueError, e :
       
   123             # invalid date/count format?
       
   124             log.warn("Unable to parse serial: %s: %s", serial, e)
       
   125 
       
   126             serial = str(value + 1)
       
   127 
       
   128             log.info("Incrementing serial: %d -> %s", value, serial)
       
   129 
       
   130             return serial
       
   131             
       
   132         log.debug("old serial=%s, value=%d, date=%s, count=%s", serial, value, date, count)
       
   133         
       
   134     # update
       
   135     if date < today :
       
   136         log.info("Updating to today: %s -> %s", date, today)
       
   137 
       
   138         # update date
       
   139         return format_serial(today, 1)
       
   140 
       
   141     elif date == today :
       
   142         # keep date, update count
       
   143         log.info("Updating today's count: %s, %s", date, count)
       
   144         
       
   145         # handle count rollover
       
   146         return next_count(value, date, count)
       
   147 
       
   148     elif date > today :
       
   149         # keep, update count
       
   150         serial = next_count(value, date, count)
       
   151 
       
   152         log.warn("Serial in future; incrementing count: %s, %s -> %s", date, count, serial)
       
   153 
       
   154         return serial
       
   155 
       
   156     else :
       
   157         raise Exception("Invalid serial: %s:%s", old_date, old_count)
       
   158     
       
   159 def process_serial (path) :
       
   160     """
    53     """
   161         Read old serial, generate new one, and update file.
    54         Read old serial, generate new one, and update file.
   162     """
    55     """
   163 
    56 
   164     # read
    57     # read
   165     if os.path.exists(path) :
    58     if os.path.exists(path) :
   166         serial = open(path).read().strip()
    59         serial = open(path).read().strip()
   167         log.debug("current serial: %s", serial)
    60         log.debug("%s: current: %s", path, serial)
   168 
    61 
   169     else :
    62     else :
   170         log.warn("Given .serial does not yet exist: %s", path)
    63         log.warn("%s: Given .serial does not yet exist", path)
   171         serial = None
    64         serial = None
   172 
    65 
   173     # update
    66     # update
   174     serial = update_serial(serial)
    67     serial = pvl.dns.serial.update_serial(serial)
   175 
    68     
   176     # write
    69     if options.noop :
   177     open(path, 'w').write(serial + '\n')
    70         log.warn("%s: %s", path, serial)
       
    71     else :
       
    72         # write
       
    73         open(path, 'w').write(serial + '\n')
   178 
    74 
   179     return serial
    75     return serial
   180 
    76 
   181 def main (argv) :
    77 def main (argv) :
   182     global options
       
   183     
       
   184     options, args = parse_options(argv)
    78     options, args = parse_options(argv)
   185 
    79 
   186     # serial files to update
    80     # serial files to update
   187     for path in args :
    81     for path in args :
   188         process_serial(path)
    82         process_serial(options, path)
   189     
    83     
   190     return 0
    84     return 0
   191 
    85 
   192 if __name__ == '__main__':
    86 if __name__ == '__main__':
   193     import sys
    87     import sys