bind.py
author Tero Marttila <terom@fixme.fi>
Sun, 12 Jul 2009 00:43:36 +0300
changeset 6 57e8168ba8c4
parent 5 86b05c0ab5cd
permissions -rw-r--r--
use FQDN for zone hosts
"""
    High-level BIND stuff
"""

from __future__ import with_statement

import bind_conf as bindc

import os.path, datetime

DEFAULT_TTL = bindc.Interval(3600)

class Settings (object) :
    """
        A set of basic settings for a zone, mostly default TTL/refresh/retry/expire/minimum settings
    """

    def __init__ (self, ttl, hostmaster, refresh, retry, expire, minimum) :
        self.ttl = ttl
        self.hostmaster = hostmaster
        self.refresh = refresh
        self.retry = retry
        self.expire = expire
        self.minimum = minimum

class AutoSerial (object) :
    """
        Automatically generate the next serial to use by loading it from a file.

        The generated serials are in YYYYMMDDXX format.
    """

    def __init__ (self, path) :
        """
            Load the current serial 

            @param path the path to the serial file
        """
        
        # store
        self.path = path
            
        # load it
        # XXX: locking
        serial = self.read()
        
        # current date
        today = datetime.date.today()

        # parse it
        if serial :
            date, code = self.parse(serial)

        else :
            date, code = today, 0
        
        # increment it
        date, code = self.next(date, code)

        # format it
        self._serial = self.build(date, code)

        # write it out
        self.write(self._serial)

    def parse (self, serial) :
        """
            Parse the given serial into a (datetime.date, code) format
        """
        
        # build it into a date
        date = datetime.date(
                year    = int(serial[0:4]),
                month   = int(serial[4:6]),
                day     = int(serial[6:8])
            )

        code = int(serial[8:])

        return date, code
   
    def next (self, date, code) :
        """
            Return the next valid serial following the given one
        """
        
        # current date
        today = datetime.date.today()

        # now to increment?
        if date < today :
            # jump to today's first serial
            date = today
            code = 0
        
        else :
            # today or overflowed into the future, just increment the code
            code += 1
        
        # code overflowed into next day?
        if code > 99 :
            date += datetime.timedelta(days=1)
            code = 0

        # ok
        return date, code
    
    def build (self, date, code) :
        """
            Build a serial code the given date/code
        """

        assert 0 <= code <= 99

        return "%s%02d" % (date.strftime("%Y%m%d"), code)

    def read (self) :
        """
            Read the current serial, returning it, or None, if not found...
        """
        
        # if it doesn't exist, default
        if not os.path.exists(self.path) :
            return None
        
        # read it
        with open(self.path) as fh :
            return fh.read().strip()
        
    def write (self, serial) :
        """
            Write a new serial
        """

        with open(self.path, 'w') as fh :
            fh.write("%s\n" % (serial, ))
    
    def serial (self) :
        """
            Return a new, unused serial code (before __init__)
        """

        return self._serial

class Domain (bindc.ZoneFile) :
    """
        A domain has a skeleton of stuff defined, but the rest is $INCLUDE'd from elsewhere, which is useful for
        multi-domain setups where the domains are mostly similar
    """

    def __init__ (self, domain, path, nameservers, mailservers, serial, settings, include=None, objs=None) :
        """
            @param domain the domain name
            @param path the path to the zone file
            @param nameservers list of nameservers as labels
            @param mailservers list of (pref, label) tuples for MX records
            @param serial the serial code to use
            @param settings the TTL/SOA settings to use
            @param include the optional zonefile to include
            @param objs the optional other objects to add to the zonefile
        """

        super(Domain, self).__init__(domain, path)
        
        # the default TTL
        self.add_directive(bindc.TTLDirective(settings.ttl))

        # the SOA record
        self.add_record(bindc.SOA(None, nameservers[0], 
                settings.hostmaster, serial, settings.refresh, settings.retry, settings.expire, settings.minimum
            ))

        # the NS records
        for label in nameservers :
            self.add_record(bindc.NS(None, label))

        # the MX records
        for pref, label in mailservers :
            self.add_record(bindc.MX(None, pref, label))
        
        # include?
        if include :
            self.add_directive(bindc.IncludeDirective(include))

        if objs :
            for obj in objs :
                self.add_obj(obj)