"""
High-level BIND stuff
"""
from __future__ import with_statement
from . import zone
import os.path, datetime
DEFAULT_TTL = zone.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 (zone.Zone) :
"""
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(zone.TTLDirective(settings.ttl))
# the SOA record
self.add_record(zone.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(zone.NS(None, label))
# the MX records
for pref, label in mailservers :
self.add_record(zone.MX(None, pref, label))
# include?
if include :
self.add_directive(zone.IncludeDirective(include))
if objs :
for obj in objs :
self.add_obj(obj)