bin/pvl.dns-serial
changeset 233 c4941645464c
child 234 472f0a422234
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/pvl.dns-serial	Thu May 16 01:03:29 2013 +0300
@@ -0,0 +1,195 @@
+#!/usr/bin/env python
+# vim: set ft=python :
+
+"""
+    Update zone serials.
+"""
+
+__version__ = '0.0.1-dev'
+
+import optparse
+import codecs
+import os.path
+from datetime import datetime
+import logging
+
+log = logging.getLogger('main')
+
+# command-line options, global state
+options = None
+
+def parse_options (argv) :
+    """
+        Parse command-line arguments.
+    """
+    
+    prog = argv[0]
+
+    parser = optparse.OptionParser(
+            prog        = prog,
+            usage       = '%prog: [options]',
+            version     = __version__,
+
+            # module docstring
+            description = __doc__,
+    )
+
+    # logging
+    general = optparse.OptionGroup(parser, "General Options")
+
+    general.add_option('-q', '--quiet',     dest='loglevel', action='store_const', const=logging.ERROR, help="Less output")
+    general.add_option('-v', '--verbose',   dest='loglevel', action='store_const', const=logging.INFO,  help="More output")
+    general.add_option('-D', '--debug',     dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output")
+
+    parser.add_option_group(general)
+
+    # defaults
+    parser.set_defaults(
+        loglevel            = logging.WARN,
+        expand              = [],
+    )
+    
+    # parse
+    options, args = parser.parse_args(argv[1:])
+
+    # configure
+    logging.basicConfig(
+        format  = prog + ': %(name)s: %(levelname)s %(funcName)s : %(message)s',
+        level   = options.loglevel,
+    )
+
+    return options, args
+
+# date fmt to use in serial
+DATE_FMT = '%Y%m%d'
+DATE_LEN = 8
+
+SERIAL_FMT = "{date:8}{count:02}"
+SERIAL_LEN = 10
+
+def format_serial (date, count) :
+    return SERIAL_FMT.format(date=date.strftime(DATE_FMT), count=count)
+
+def next_count (value, date, count) :
+    """
+        Return serial with next count.
+    """
+
+    count += 1
+
+    # check
+    if count > 99 :
+        serial = str(value + 1)
+
+        log.warn("Serial count rollover: %s, %s; fallback -> %s", date, count, serial)
+
+        return serial
+
+    return format_serial(date, count)
+
+def update_serial (serial) :
+    """
+        Update new serial number into given file, based on date.
+    """
+    
+    today = datetime.now().date()
+
+    # handle
+    if not serial :
+        # fresh
+        log.info("Setting initial serial: %s01", today)
+        
+        return format_serial(today, 1)
+
+    elif len(serial) != SERIAL_LEN :
+        log.warn("Invalid serial format: %s", serial)
+        
+        value = int(serial)
+        serial = str(value + 1)
+
+        log.info("Incrementing serial: %d -> %s", value, serial)
+
+        return serial
+
+    else :
+        # parse
+        value = int(serial)
+
+        try :
+            date = datetime.strptime(serial[:DATE_LEN], DATE_FMT).date()
+            count = int(serial[DATE_LEN:])
+
+        except ValueError, e :
+            # invalid date/count format?
+            log.warn("Unable to parse serial: %s: %s", serial, e)
+
+            serial = str(value + 1)
+
+            log.info("Incrementing serial: %d -> %s", value, serial)
+
+            return serial
+            
+        log.debug("old serial=%s, value=%d, date=%s, count=%s", serial, value, date, count)
+        
+    # update
+    if date < today :
+        log.info("Updating to today: %s -> %s", date, today)
+
+        # update date
+        return format_serial(today, 1)
+
+    elif date == today :
+        # keep date, update count
+        log.info("Updating today's count: %s, %s", date, count)
+        
+        # handle count rollover
+        return next_count(value, date, count)
+
+    elif date > today :
+        # keep, update count
+        serial = next_count(value, date, count)
+
+        log.warn("Serial in future; incrementing count: %s, %s -> %s", date, count, serial)
+
+        return serial
+
+    else :
+        raise Exception("Invalid serial: %s:%s", old_date, old_count)
+    
+def process_serial (path) :
+    """
+        Read old serial, generate new one, and update file.
+    """
+
+    # read
+    if os.path.exists(path) :
+        serial = open(path).read().strip()
+        log.debug("current serial: %s", serial)
+
+    else :
+        log.warn("Given .serial does not yet exist: %s", path)
+        serial = None
+
+    # update
+    serial = update_serial(serial)
+
+    # write
+    open(path, 'w').write(serial + '\n')
+
+    return serial
+
+def main (argv) :
+    global options
+    
+    options, args = parse_options(argv)
+
+    # serial files to update
+    for path in args :
+        process_serial(path)
+    
+    return 0
+
+if __name__ == '__main__':
+    import sys
+
+    sys.exit(main(sys.argv))