# HG changeset patch # User Tero Marttila # Date 1247354074 -10800 # Node ID 2156906bfbf149374ff990a57a00fd626dc5a4c5 # Parent 46d36bc330868c3f7929ea85ae14ca20f08e8580 reorganize modules into packages diff -r 46d36bc33086 -r 2156906bfbf1 pvl/config/bind.py --- a/pvl/config/bind.py Sun Jul 12 02:14:08 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,187 +0,0 @@ -""" - 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) diff -r 46d36bc33086 -r 2156906bfbf1 pvl/config/bind/model.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pvl/config/bind/model.py Sun Jul 12 02:14:34 2009 +0300 @@ -0,0 +1,188 @@ +""" + 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) + diff -r 46d36bc33086 -r 2156906bfbf1 pvl/config/bind/zone.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pvl/config/bind/zone.py Sun Jul 12 02:14:34 2009 +0300 @@ -0,0 +1,355 @@ +""" + Configuration file output for the ISC DNS server: BIND +""" + +from .. import file + +class Item (file.Item) : + """ + Item that knows how to format comments + """ + + COMMENT_PREFIX = ';' + +class Zone (Item, file.Contents) : + """ + A zone containing a bunch of directives, resource records and comments + """ + + def __init__ (self, ttl=None, comment=None) : + """ + @param name the name of the zonefile, for status stuff + @param path the path to the zonefile + @param ttl default TTL to use + """ + + Item.__init__(self, [comment]) + file.Contents.__init__(self) + + if ttl : + self.add(TTLDirective(ttl)) + +class Comment (Item) : + """ + A comment, is, well, a comment :) + """ + + def __init__ (self, comment) : + """ + @param comment the comment string + """ + + Item.__init__(self, [comment]) + + # only need to iterate over our comments + iter_lines = Item.iter_comments + +class Label (object) : + """ + A label, as used in a ResourceRecord, either as the label, or the rdata for various resource types + + You can also use strs, this just implements a __str__ method + """ + + def __init__ (self, label) : + """ + @param label the literal label to use + """ + + self.label = label + + def __str__ (self) : + return self.label + +class Origin (Label) : + """ + A label that represents the zone's origin + """ + + def __init__ (self) : + pass + + def __str__ (self) : + return '@' + +class FQDN (Label) : + """ + A label that represents the given external domain (i.e. this adds the . to the end that people always forget). + """ + + def __init__ (self, fqdn) : + self.fqdn = fqdn + + def __str__ (self) : + return "%s." % (self.fqdn, ) + +class Interval (object) : + """ + A time interval suitable for use in SOA records + """ + + def __init__ (self, s=None, m=None, h=None, d=None) : + """ + @param s seconds + @param m minutes + @param h hours + @param d days + """ + + self.s = s + self.m = m + self.h = h + self.d = d + + def __str__ (self) : + """ + If only seconds were given, just return those directly, otherwise, apply units + """ + + if self.s and not self.m and not self.h and not self.d : + return str(self.s) + + else : + return "%s%s%s%s" % ( + "%ds" % self.s if self.s else '', + "%dm" % self.m if self.m else '', + "%dh" % self.h if self.h else '', + "%dd" % self.d if self.d else '' + ) + +class ResourceRecord (Item) : + """ + A generic resource record for a BIND zone file + """ + + def __init__ (self, label, type, rdata, cls='IN', ttl=None, **kwargs) : + """ + @param label the "name" of this record, or None to referr to the previous record's label + @param type the type as a string ('A', 'TXT', etc.) + @param rdata the rdata, as a raw string + @param cls the class, e.g. 'IN' + @param ttl the time-to-live value in seconds, or None to omit it + """ + + super(ResourceRecord, self).__init__(**kwargs) + + self.label = label + self.type = type + self.rdata = rdata + self.cls = cls + self.ttl = ttl + + def iter_lines (self) : + """ + Just format the lines, eh + """ + + # prefix comments + for line in self.iter_comments() : + yield line + + # then format the line + # XXX: TTL? + yield "%-30s %-4s%-4s %-8s %s" % (self.label if self.label is not None else '', str(self.ttl) if self.ttl else '', self.cls, self.type, self.rdata) + +class SOA (ResourceRecord) : + """ + "Identifies the start of a zone of authority", must be the first record + """ + + def __init__ (self, label, primary_ns, hostmaster, serial, refresh, retry, expire, minimum, **kwargs) : + """ + @param label the "name" of the zone, usually ORIGIN + @param primary_ns the address of the primary NS server + @param hostmaster the mailbox of the zone's hostmaster + @param serial the serial number of the zone as an integer + @param refresh time interval between zone refreshes in seconds + @param retry time interval between retrys for failed refreshes + @param expire time interval before zone data can no longer be considered authorative + @param minimum minimum TTL for RRs in this zone + """ + + super(SOA, self).__init__(label, 'SOA', "%s %s ( %s %s %s %s %s )" % ( + primary_ns, hostmaster, serial, refresh, retry, expire, minimum + ), **kwargs) + +class A (ResourceRecord) : + """ + An IPv4 forward address + """ + + def __init__ (self, label, addr, **kwargs) : + """ + @param label the "name" of the address + @param addr the IPv4 target address + """ + + assert(addr.is_v4()) + + super(A, self).__init__(label, 'A', addr, **kwargs) + +class AAAA (ResourceRecord) : + """ + An IPv6 forward address + """ + + def __init__ (self, label, addr, **kwargs) : + """ + @param label the "name" of the address + @param addr the IPv6 target address + """ + + assert(addr.is_v6()) + + super(AAAA, self).__init__(label, 'AAAA', addr.strCompressed(), **kwargs) + +class CNAME (ResourceRecord) : + """ + A canonical-name alias + """ + + def __init__ (self, label, target, **kwargs) : + """ + @param label the "name" of the alias + @param target the alias target + """ + + super(CNAME, self).__init__(label, 'CNAME', target, **kwargs) + +class TXT (ResourceRecord) : + """ + A human-readable information record + """ + + def __init__ (self, label, text, **kwargs) : + """ + @param label the "name" of the text record + @param text the text data, shouldn't contain any quotes... + """ + + # XXX: escaping? + super(TXT, self).__init__(label, 'TXT', '"%s"' % text, **kwargs) + +class MX (ResourceRecord) : + """ + A mail-exchange definition + """ + + def __init__ (self, label, pref, exchange, **kwargs) : + """ + @param label the "name" of the domain to handle mail for + @param pref the numerical preference for this exchange + @param exchange the domain name of the mail exchange (SMTP server) + """ + + super(MX, self).__init__(label, 'MX', "%d %s" % (pref, exchange), **kwargs) + +class NS (ResourceRecord) : + """ + An authorative name server + """ + + def __init__ (self, label, nsname, **kwargs) : + """ + @param label the "name" of the domain to have a nameserver for + @param nsname the name of the nameserver + """ + + super(NS, self).__init__(label, 'NS', nsname) + +class PTR (ResourceRecord) : + """ + An IPv4/IPv6 reverse address + """ + + def __init__ (self, addr, name, **kwargs) : + """ + @param addr the addr.IP to map via in-addr.arpa + @param name the name to map the address to + """ + + # XXX: quick hack, this gives an absolute name + label = addr.reverseName() + + super(PTR, self).__init__(label, 'PTR', name) + +class Directive (Item) : + """ + Special directives that can be used in zone files to control behaviour + """ + + def __init__ (self, name, *args, **kwargs) : + """ + @param name the $NAME bit + @param args optional list of space-seprated arguments, Nones are ignored + """ + + super(Directive, self).__init__(**kwargs) + + self.name = name + self.args = [arg for arg in args if arg is not None] + + def iter_lines (self) : + # prefix comments + for line in self.iter_comments() : + yield line + + # then format it + yield "$%s%s" % (self.name, (' ' + ' '.join(str(arg) for arg in self.args)) if self.args else '') + +class OriginDirective (Directive) : + """ + Set the origin used to resolve the zone's labels. + + Note that the origin label is not absolute by default - use FQDN for that + """ + + def __init__ (self, origin, **kwargs) : + """ + @param origin the origin label + """ + + super(OriginDirective, self).__init__('ORIGIN', origin, **kwargs) + +class TTLDirective (Directive) : + """ + Set the TTL used for records by default + """ + + def __init__ (self, ttl, **kwargs) : + """ + @param ttl the new ttl to use + """ + + super(TTLDirective, self).__init__('TTL', ttl, **kwargs) + +class IncludeDirective (Directive) : + """ + Include another zoen file, optionally with a different origin + """ + + def __init__ (self, filename, origin=None, **kwargs) : + """ + @param filename the zone file to include + @param origin the optional origin to process the zonefile with + """ + + super(IncludeDirective, self).__init__('INCLUDE', filename, origin, **kwargs) + +class GenerateDirective (Directive) : + """ + Generate a bunch of numbered records using an expression for the label and rdata. + + At the simplest, any "$" in the expression is replaced with the value of the iterator. + """ + + def __init__ (self, range, lhs, type, rhs, ttl=None, cls=None, **kwargs) : + """ + @param range (start, stop, step) tuple + @param lhs expression to generate the label + @param type the resource record type + @param rhs expression to generate the rdata + """ + + super(GenerateDirective, self).__init__('GENERATE', '%d-%d' % range, lhs, ttl, cls, type, rhs, **kwargs) + diff -r 46d36bc33086 -r 2156906bfbf1 pvl/config/bind_conf.py --- a/pvl/config/bind_conf.py Sun Jul 12 02:14:08 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,395 +0,0 @@ -""" - Configuration file output for the ISC DNS server: BIND -""" - -import conf - -class Object (conf.ConfObject) : - """ - Our own version of ConfObject that knows how to format comments - """ - - def _fmt_comments (self) : - """ - Format comments using the standard format: - ";" - """ - - for comment in self.comments : - if comment is not None : - yield "; %s" % (comment, ) - -class ZoneFile (Object, conf.File) : - """ - A zone file containing a bunch of directives, resource records and comments - """ - - def __init__ (self, name, path, ttl=None, comment=None) : - """ - @param name the name of the zonefile, for status stuff - @param path the path to the zonefile - @param ttl default TTL to use - @param comment optional comments - """ - - Object.__init__(self, comment) - conf.File.__init__(self, name, path) - - # init - self.objects = [] - - if ttl : - self.add_directive(TTLDirective(ttl)) - - def add_obj (self, obj) : - """ - Add an Object onto the list of things to include in this zone file - """ - - self.objects.append(obj) - - # various aliases... - add_comment = add_record = add_directive = add_obj - - def fmt_lines (self) : - """ - Just format all our objects - """ - - # prefix comments - for line in self._fmt_comments() : - yield line - - # and then all objects - for obj in self.objects : - # ...and their lines - for line in obj.fmt_lines() : - yield line - -class Comment (Object) : - """ - A comment, is, well, a comment :) - """ - - def __init__ (self, comment) : - """ - @param comment the comment string - """ - - Object.__init__(self, [comment]) - - def fmt_lines (self) : - """ - Yield a single line with the comment - """ - - return self._fmt_comments() - -class Label (object) : - """ - A label, as used in a ResourceRecord, either as the label, or the rdata for various resource types - - You can also use strs, this just implements a __str__ method - """ - - def __init__ (self, label) : - """ - @param label the literal label to use - """ - - self.label = label - - def __str__ (self) : - return self.label - -class Origin (Label) : - """ - A label that represents the zone's origin - """ - - def __init__ (self) : - pass - - def __str__ (self) : - return '@' - -class FQDN (Label) : - """ - A label that represents the given external domain (i.e. this adds the . to the end that people always forget). - """ - - def __init__ (self, fqdn) : - self.fqdn = fqdn - - def __str__ (self) : - return "%s." % (self.fqdn, ) - -class Interval (object) : - """ - A time interval suitable for use in SOA records - """ - - def __init__ (self, s=None, m=None, h=None, d=None) : - """ - @param s seconds - @param m minutes - @param h hours - @param d days - """ - - self.s = s - self.m = m - self.h = h - self.d = d - - def __str__ (self) : - """ - If only seconds were given, just return those directly, otherwise, apply units - """ - - if self.s and not self.m and not self.h and not self.d : - return str(self.s) - - else : - return "%s%s%s%s" % ( - "%ds" % self.s if self.s else '', - "%dm" % self.m if self.m else '', - "%dh" % self.h if self.h else '', - "%dd" % self.d if self.d else '' - ) - -class ResourceRecord (Object) : - """ - A generic resource record for a BIND zone file - """ - - def __init__ (self, label, type, rdata, cls='IN', ttl=None, **kwargs) : - """ - @param label the "name" of this record, or None to referr to the previous record's label - @param type the type as a string ('A', 'TXT', etc.) - @param rdata the rdata, as a raw string - @param cls the class, e.g. 'IN' - @param ttl the time-to-live value in seconds, or None to omit it - """ - - super(ResourceRecord, self).__init__(**kwargs) - - self.label = label - self.type = type - self.rdata = rdata - self.cls = cls - self.ttl = ttl - - def fmt_lines (self) : - """ - Just format the lines, eh - """ - - # prefix comments - for line in self._fmt_comments() : - yield line - - # then format the line - # XXX: TTL? - yield "%-30s %-4s%-4s %-8s %s" % (self.label if self.label is not None else '', str(self.ttl) if self.ttl else '', self.cls, self.type, self.rdata) - -class SOA (ResourceRecord) : - """ - "Identifies the start of a zone of authority", must be the first record - """ - - def __init__ (self, label, primary_ns, hostmaster, serial, refresh, retry, expire, minimum, **kwargs) : - """ - @param label the "name" of the zone, usually ORIGIN - @param primary_ns the address of the primary NS server - @param hostmaster the mailbox of the zone's hostmaster - @param serial the serial number of the zone as an integer - @param refresh time interval between zone refreshes in seconds - @param retry time interval between retrys for failed refreshes - @param expire time interval before zone data can no longer be considered authorative - @param minimum minimum TTL for RRs in this zone - """ - - super(SOA, self).__init__(label, 'SOA', "%s %s ( %s %s %s %s %s )" % ( - primary_ns, hostmaster, serial, refresh, retry, expire, minimum - ), **kwargs) - -class A (ResourceRecord) : - """ - An IPv4 forward address - """ - - def __init__ (self, label, addr, **kwargs) : - """ - @param label the "name" of the address - @param addr the IPv4 target address - """ - - assert(addr.is_v4()) - - super(A, self).__init__(label, 'A', addr, **kwargs) - -class AAAA (ResourceRecord) : - """ - An IPv6 forward address - """ - - def __init__ (self, label, addr, **kwargs) : - """ - @param label the "name" of the address - @param addr the IPv6 target address - """ - - assert(addr.is_v6()) - - super(AAAA, self).__init__(label, 'AAAA', addr.strCompressed(), **kwargs) - -class CNAME (ResourceRecord) : - """ - A canonical-name alias - """ - - def __init__ (self, label, target, **kwargs) : - """ - @param label the "name" of the alias - @param target the alias target - """ - - super(CNAME, self).__init__(label, 'CNAME', target, **kwargs) - -class TXT (ResourceRecord) : - """ - A human-readable information record - """ - - def __init__ (self, label, text, **kwargs) : - """ - @param label the "name" of the text record - @param text the text data, shouldn't contain any quotes... - """ - - super(TXT, self).__init__(label, 'TXT', '"%s"' % text, **kwargs) - -class MX (ResourceRecord) : - """ - A mail-exchange definition - """ - - def __init__ (self, label, pref, exchange, **kwargs) : - """ - @param label the "name" of the domain to handle mail for - @param pref the numerical preference for this exchange - @param exchange the domain name of the mail exchange (SMTP server) - """ - - super(MX, self).__init__(label, 'MX', "%d %s" % (pref, exchange), **kwargs) - -class NS (ResourceRecord) : - """ - An authorative name server - """ - - def __init__ (self, label, nsname, **kwargs) : - """ - @param label the "name" of the domain to have a nameserver for - @param nsname the name of the nameserver - """ - - super(NS, self).__init__(label, 'NS', nsname) - -class PTR (ResourceRecord) : - """ - An IPv4/IPv6 reverse address - """ - - def __init__ (self, addr, name, **kwargs) : - """ - @param addr the addr.IP to map via in-addr.arpa - @param name the name to map the address to - """ - - # XXX: quick hack, this gives an absolute name - label = addr.reverseName() - - super(PTR, self).__init__(label, 'PTR', name) - -class Directive (Object) : - """ - Special directives that can be used in zone files to control behaviour - """ - - def __init__ (self, name, *args, **kwargs) : - """ - @param name the $NAME bit - @param args optional list of space-seprated arguments, Nones are ignored - """ - - super(Directive, self).__init__(**kwargs) - - self.name = name - self.args = [arg for arg in args if arg is not None] - - def fmt_lines (self) : - # prefix comments - for line in self._fmt_comments() : - yield line - - # then format it - yield "$%s%s" % (self.name, (' ' + ' '.join(str(arg) for arg in self.args)) if self.args else '') - -class OriginDirective (Directive) : - """ - Set the origin used to resolve the zone's labels. - - Note that the origin label is not absolute by default - use FQDN for that - """ - - def __init__ (self, origin, **kwargs) : - """ - @param origin the origin label - """ - - super(OriginDirective, self).__init__('ORIGIN', origin, **kwargs) - -class TTLDirective (Directive) : - """ - Set the TTL used for records by default - """ - - def __init__ (self, ttl, **kwargs) : - """ - @param ttl the new ttl to use - """ - - super(TTLDirective, self).__init__('TTL', ttl, **kwargs) - -class IncludeDirective (Directive) : - """ - Include another zoen file, optionally with a different origin - """ - - def __init__ (self, filename, origin=None, **kwargs) : - """ - @param filename the zone file to include - @param origin the optional origin to process the zonefile with - """ - - super(IncludeDirective, self).__init__('INCLUDE', filename, origin, **kwargs) - -class GenerateDirective (Directive) : - """ - Generate a bunch of numbered records using an expression for the label and rdata. - - At the simplest, any "$" in the expression is replaced with the value of the iterator. - """ - - def __init__ (self, range, lhs, type, rhs, ttl=None, cls=None, **kwargs) : - """ - @param range (start, stop, step) tuple - @param lhs expression to generate the label - @param type the resource record type - @param rhs expression to generate the rdata - """ - - super(GenerateDirective, self).__init__('GENERATE', '%d-%d' % range, lhs, ttl, cls, type, rhs, **kwargs) - diff -r 46d36bc33086 -r 2156906bfbf1 pvl/config/dhcp.py --- a/pvl/config/dhcp.py Sun Jul 12 02:14:08 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -""" - Higher-level DHCP config structure model -""" - -import dhcp_conf as dhcpc - -class Config (dhcpc.ConfFile) : - """ - A full configuration file - """ - - def __init__ (self, name=dhcpc.ConfFile.DEFAULT_NAME, path=dhcpc.ConfFile.DEFAULT_PATH, - settings=None, options=None, shared_network=False, subnets=None, hosts=None, comment=None - ) : - """ - Create a full configuration file for the given settings: - - settings: a { name: value } mappping of general settings to set - options: a { opt_name: opt_value } mapping of options to set - shared_network: define the subnets as a shared network of the given name - subnets: an iterable of Subnet's to define - hosts: an iterable of Host's to define - - """ - - dhcpc.ConfFile.__init__(self, name, path, comment=comment) - - # define global settings - if settings : - self.add_params(dhcpc.Parameter(setting, value) for setting, value in settings.iteritems()) - - # define global options - if options : - self.add_params(dhcpc.Option(option, value) for option, value in options.iteritems()) - - # the shared-network section, or a series of subnets - if shared_network : - self.add_decl(dhcpc.SharedNetwork(shared_network, decls=subnets)) - - elif subnets : - self.add_decls(subnets) - - # hosts section - if hosts : - self.add_decls(hosts) - -class Subnet (dhcpc.Subnet) : - """ - A subnet declaration with a router, and optionally a dynamic address pool, and allow/deny unknown clients - """ - - def __init__ (self, subnet, router_idx=1, range=None, unknown_clients=None, comment=None) : - """ - @param subnet the addr.IP representing the subnet - @param router_idx the subnet[index] of the default gateway - @param range optional (from_idx, to_idx) to define a dhcp pool - @param unknown_clients optional 'allow'/'deny' to set a policy for unknown clients - """ - - # validate - if unknown_clients : - assert unknown_clients in ('allow', 'deny') - - super(Subnet, self).__init__(subnet, params=[ - dhcpc.Option("routers", subnet[router_idx]), - dhcpc.Parameter("range", subnet[range[0]], subnet[range[1]]) if range else None, - dhcpc.Parameter(unknown_clients, "unknown-clients") if unknown_clients else None, - ], comment=comment) - - -class Host (dhcpc.Host) : - """ - A host declaration with a hardware address and a IP address - """ - - def __init__ (self, hostname, mac_addr, ip_addr, comment=None) : - super(Host, self).__init__(hostname, params=[ - dhcpc.Parameter("hardware ethernet", mac_addr), - dhcpc.Parameter("fixed-address", ip_addr) - ], comment=comment) - diff -r 46d36bc33086 -r 2156906bfbf1 pvl/config/dhcp_conf.py --- a/pvl/config/dhcp_conf.py Sun Jul 12 02:14:08 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,341 +0,0 @@ -""" - Configuration file output for the ISC DHCP server -""" - -import conf - -import itertools - -class Object (conf.ConfObject) : - """ - Our version of ConfObject - """ - - def _fmt_comments (self) : - """ - Format our comment lines - """ - - for comment in self.comments : - if comment is not None : - yield "# %s" % (comment, ) - - -class Comment (Object) : - """ - A comment, is, well, a comment :) - """ - - def __init__ (self, comment) : - """ - @param comment the comment string - """ - - Object.__init__(self, [comment]) - - def fmt_lines (self) : - """ - Yield a single line with the comment - """ - - return self._fmt_comments() - -class _Section (Object) : - """ - Base implementation of Section, but doesn't format comments in output (inheriting class can define how that happens) - """ - - def __init__ (self, params=None, decls=None, comment=None) : - """ - If params/decls are given, those are the used as the initial contents of this section - - If a comment is given, then it will be formatted before the section's stuff - """ - - Object.__init__(self, [comment]) - - self.params = params or [] - self.decls = decls or [] - - def add_param (self, param) : - """ - Add the given Parameter to the end of this section's params - """ - - self.params.append(param) - - def add_params (self, params) : - for param in params : - self.add_param(param) - - def add_decl (self, decl) : - """ - Add the given Declaration to the end of this section's decls - """ - - self.decls.append(decl) - - def add_decls (self, decls) : - for decl in decls : - self.add_decl(decl) - - def fmt_lines (self) : - """ - Format all of our params and decls, in that order - """ - - # then output each content line - for stmt in itertools.chain(self.params, self.decls) : - # skip Nones - if stmt is None : - continue - - for line in stmt.fmt_lines() : - yield line - -class Section (_Section) : - """ - A section holds a list of params and a list of decls, plus some comments at the beginning of the section - """ - - def fmt_lines (self) : - """ - Format all of our comments, and then super - """ - - # comments - for line in self._fmt_comments() : - yield line - - # section stuff - for line in _Section.fmt_lines(self) : - yield line - -class ConfFile (Section, conf.File) : - DEFAULT_NAME = "dhcpd.conf" - DEFAULT_PATH = "/etc/dhcp3/dhcpd.conf" - - def __init__ (self, name=DEFAULT_NAME, path=DEFAULT_PATH, params=None, decls=None, comment=None) : - """ - Initialize the dhcpd config file, but don't open it yet. - """ - - conf.File.__init__(self, name, path) - Section.__init__(self, params, decls, comment) - -class Statement (Object) : - """ - A statement is a single line in the config file - """ - - def __init__ (self, name, *args, **kwargs) : - """ - Arguments given as None will be ignored. - - A comment can be given as a keyword argument - """ - - if kwargs : assert len(kwargs) == 1 and 'comment' in kwargs - - Object.__init__(self, [kwargs.get('comment')]) - - self.name = name - self.args = [arg for arg in args if arg is not None] - - def _fmt_arg (self, arg) : - """ - Formats a arg for use in output, the following types are supported: - - list/tuple/iter: results in a comma-and-space separated list of formatted values - unicode: results in an encoded str - str: results in the string itself, quoted if needed - other: attempt to convert to a str, and then format that - """ - - # format lists specially - # XXX: iterators? - if isinstance(arg, (list, tuple)) : - # recurse as a comma-and-space separated list - return ', '.join(self._fmt_arg(a) for a in arg) - - elif isinstance(arg, Literal) : - # use what it specifies - return arg.fmt_arg() - - elif isinstance(arg, unicode) : - # recurse with the str version - # XXX: what encoding to use? - return self._fmt_arg(arg.encode('utf8')) - - elif isinstance(arg, str) : - # XXX: quoting - return arg - - else : - # try and use it as a string - return self._fmt_arg(str(arg)) - - def _fmt_data (self) : - """ - Formats the statement name/params as a single line, ignoring None - """ - - return "%s%s" % (self.name, (' ' + ' '.join(self._fmt_arg(a) for a in self.args)) if self.args else '') - -class Literal (Statement) : - """ - A literal is something that goes into the config file as-is, with no formatting or escaping applied. - """ - - def __init__ (self, literal) : - self.literal = literal - - def fmt_arg (self) : - return self.literal - - def fmt_lines (self) : - yield self.literal - -class Parameter (Statement) : - """ - A parameter is a single statement that configures the behaviour of something. - - Parameters have a name, and optionally, a number of arguments, and are formatted as statements terminated with - a semicolon. For convenience, params/decls that are None are ignored. - - The parameter will be formatted like this: - [ [ ... ] ] ";" - """ - - def fmt_lines (self) : - """ - Yields a single ;-terminated line - """ - - # comments - for line in self._fmt_comments() : - yield line - - # the line - yield "%s;" % self._fmt_data() - -class Declaration (_Section, Statement) : - """ - A declaration begins like a statement (with name and args), but then contains a curly-braces-delimited block - that acts like a Section. - - [ [ ... ] ] { - [
] - } - - """ - - def __init__ (self, name, args=[], params=None, decls=None, comment=None) : - """ - The name/args will be formatted as in Statement, but params should be an iterable of Parameters, and decls - an iterable of Declarations. - """ - - # init the statement bit - _Section.__init__(self, params, decls) - Statement.__init__(self, name, *args, **dict(comment=comment)) - - def fmt_lines (self) : - """ - Yields a header line, a series of indented body lines, and the footer line - """ - - # comments - for line in self._fmt_comments() : - yield line - - # the header to open the block - yield "%s {" % self._fmt_data() - - # then output the section stuff, indented - for line in _Section.fmt_lines(self) : - yield "\t%s" % line - - # and then close the block - yield "}" - -class SharedNetwork (Declaration) : - """ - A shared-network declaration is used to define a set of subnets that share the same physical network, - optionally with some shared params. - - shared-network { - [ parameters ] - [ declarations ] - } - """ - - def __init__ (self, name, *args, **kwargs) : - """ - @param name the name of the shared-subnet - """ - - super(SharedNetwork, self).__init__("shared-network", [name], *args, **kwargs) - -class Subnet (Declaration) : - """ - A subnet is used to provide the information about a subnet required to identify whether or not an IP address is - on that subnet, and may also be used to specify parameters/declarations for that subnet. - - subnet netmask { - [ parameters ] - [ declarations ] - } - """ - - def __init__ (self, network, *args, **kwargs) : - """ - @param network the addr.Network for the subnet - """ - - super(Subnet, self).__init__("subnet", [network.net(), "netmask", network.netmask()], *args, **kwargs) - -class Group (Declaration) : - """ - A group is simply used to apply a set of parameters to a set of declarations. - - group { - [ parameters ] - [ declarations ] - } - """ - - def __init__ (self, *args, **kwargs) : - super(Group, self).__init__("group", [], *args, **kwargs) - - -class Host (Declaration) : - """ - A host is used to match a request against specific host, and then apply settings for that host. - - The "hostname" is the DHCP name to identify the host. - - If no dhcp-client-identifier option is specified in the parameters, then the host is matched using the - "hardware" parameter. - - host { - [ parameters ] - [ declarations ] - } - """ - - def __init__ (self, hostname, *args, **kwargs) : - super(Host, self).__init__("host", [hostname], *args, **kwargs) - -class Option (Parameter) : - """ - A generic 'option' parameter for a dhcpd.conf file - """ - - def __init__ (self, name, *args, **kwargs) : - """ - Formatted as a Satement with a name of "option ". - """ - - super(Option, self).__init__("option %s" % name, *args, **kwargs) - diff -r 46d36bc33086 -r 2156906bfbf1 pvl/config/dhcpd/conf.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pvl/config/dhcpd/conf.py Sun Jul 12 02:14:34 2009 +0300 @@ -0,0 +1,341 @@ +""" + Configuration file output for the ISC DHCP server +""" + +import conf + +import itertools + +class Object (conf.ConfObject) : + """ + Our version of ConfObject + """ + + def _fmt_comments (self) : + """ + Format our comment lines + """ + + for comment in self.comments : + if comment is not None : + yield "# %s" % (comment, ) + + +class Comment (Object) : + """ + A comment, is, well, a comment :) + """ + + def __init__ (self, comment) : + """ + @param comment the comment string + """ + + Object.__init__(self, [comment]) + + def fmt_lines (self) : + """ + Yield a single line with the comment + """ + + return self._fmt_comments() + +class _Section (Object) : + """ + Base implementation of Section, but doesn't format comments in output (inheriting class can define how that happens) + """ + + def __init__ (self, params=None, decls=None, comment=None) : + """ + If params/decls are given, those are the used as the initial contents of this section + + If a comment is given, then it will be formatted before the section's stuff + """ + + Object.__init__(self, [comment]) + + self.params = params or [] + self.decls = decls or [] + + def add_param (self, param) : + """ + Add the given Parameter to the end of this section's params + """ + + self.params.append(param) + + def add_params (self, params) : + for param in params : + self.add_param(param) + + def add_decl (self, decl) : + """ + Add the given Declaration to the end of this section's decls + """ + + self.decls.append(decl) + + def add_decls (self, decls) : + for decl in decls : + self.add_decl(decl) + + def fmt_lines (self) : + """ + Format all of our params and decls, in that order + """ + + # then output each content line + for stmt in itertools.chain(self.params, self.decls) : + # skip Nones + if stmt is None : + continue + + for line in stmt.fmt_lines() : + yield line + +class Section (_Section) : + """ + A section holds a list of params and a list of decls, plus some comments at the beginning of the section + """ + + def fmt_lines (self) : + """ + Format all of our comments, and then super + """ + + # comments + for line in self._fmt_comments() : + yield line + + # section stuff + for line in _Section.fmt_lines(self) : + yield line + +class ConfFile (Section, conf.File) : + DEFAULT_NAME = "dhcpd.conf" + DEFAULT_PATH = "/etc/dhcp3/dhcpd.conf" + + def __init__ (self, name=DEFAULT_NAME, path=DEFAULT_PATH, params=None, decls=None, comment=None) : + """ + Initialize the dhcpd config file, but don't open it yet. + """ + + conf.File.__init__(self, name, path) + Section.__init__(self, params, decls, comment) + +class Statement (Object) : + """ + A statement is a single line in the config file + """ + + def __init__ (self, name, *args, **kwargs) : + """ + Arguments given as None will be ignored. + + A comment can be given as a keyword argument + """ + + if kwargs : assert len(kwargs) == 1 and 'comment' in kwargs + + Object.__init__(self, [kwargs.get('comment')]) + + self.name = name + self.args = [arg for arg in args if arg is not None] + + def _fmt_arg (self, arg) : + """ + Formats a arg for use in output, the following types are supported: + + list/tuple/iter: results in a comma-and-space separated list of formatted values + unicode: results in an encoded str + str: results in the string itself, quoted if needed + other: attempt to convert to a str, and then format that + """ + + # format lists specially + # XXX: iterators? + if isinstance(arg, (list, tuple)) : + # recurse as a comma-and-space separated list + return ', '.join(self._fmt_arg(a) for a in arg) + + elif isinstance(arg, Literal) : + # use what it specifies + return arg.fmt_arg() + + elif isinstance(arg, unicode) : + # recurse with the str version + # XXX: what encoding to use? + return self._fmt_arg(arg.encode('utf8')) + + elif isinstance(arg, str) : + # XXX: quoting + return arg + + else : + # try and use it as a string + return self._fmt_arg(str(arg)) + + def _fmt_data (self) : + """ + Formats the statement name/params as a single line, ignoring None + """ + + return "%s%s" % (self.name, (' ' + ' '.join(self._fmt_arg(a) for a in self.args)) if self.args else '') + +class Literal (Statement) : + """ + A literal is something that goes into the config file as-is, with no formatting or escaping applied. + """ + + def __init__ (self, literal) : + self.literal = literal + + def fmt_arg (self) : + return self.literal + + def fmt_lines (self) : + yield self.literal + +class Parameter (Statement) : + """ + A parameter is a single statement that configures the behaviour of something. + + Parameters have a name, and optionally, a number of arguments, and are formatted as statements terminated with + a semicolon. For convenience, params/decls that are None are ignored. + + The parameter will be formatted like this: + [ [ ... ] ] ";" + """ + + def fmt_lines (self) : + """ + Yields a single ;-terminated line + """ + + # comments + for line in self._fmt_comments() : + yield line + + # the line + yield "%s;" % self._fmt_data() + +class Declaration (_Section, Statement) : + """ + A declaration begins like a statement (with name and args), but then contains a curly-braces-delimited block + that acts like a Section. + + [ [ ... ] ] { + [
] + } + + """ + + def __init__ (self, name, args=[], params=None, decls=None, comment=None) : + """ + The name/args will be formatted as in Statement, but params should be an iterable of Parameters, and decls + an iterable of Declarations. + """ + + # init the statement bit + _Section.__init__(self, params, decls) + Statement.__init__(self, name, *args, **dict(comment=comment)) + + def fmt_lines (self) : + """ + Yields a header line, a series of indented body lines, and the footer line + """ + + # comments + for line in self._fmt_comments() : + yield line + + # the header to open the block + yield "%s {" % self._fmt_data() + + # then output the section stuff, indented + for line in _Section.fmt_lines(self) : + yield "\t%s" % line + + # and then close the block + yield "}" + +class SharedNetwork (Declaration) : + """ + A shared-network declaration is used to define a set of subnets that share the same physical network, + optionally with some shared params. + + shared-network { + [ parameters ] + [ declarations ] + } + """ + + def __init__ (self, name, *args, **kwargs) : + """ + @param name the name of the shared-subnet + """ + + super(SharedNetwork, self).__init__("shared-network", [name], *args, **kwargs) + +class Subnet (Declaration) : + """ + A subnet is used to provide the information about a subnet required to identify whether or not an IP address is + on that subnet, and may also be used to specify parameters/declarations for that subnet. + + subnet netmask { + [ parameters ] + [ declarations ] + } + """ + + def __init__ (self, network, *args, **kwargs) : + """ + @param network the addr.Network for the subnet + """ + + super(Subnet, self).__init__("subnet", [network.net(), "netmask", network.netmask()], *args, **kwargs) + +class Group (Declaration) : + """ + A group is simply used to apply a set of parameters to a set of declarations. + + group { + [ parameters ] + [ declarations ] + } + """ + + def __init__ (self, *args, **kwargs) : + super(Group, self).__init__("group", [], *args, **kwargs) + + +class Host (Declaration) : + """ + A host is used to match a request against specific host, and then apply settings for that host. + + The "hostname" is the DHCP name to identify the host. + + If no dhcp-client-identifier option is specified in the parameters, then the host is matched using the + "hardware" parameter. + + host { + [ parameters ] + [ declarations ] + } + """ + + def __init__ (self, hostname, *args, **kwargs) : + super(Host, self).__init__("host", [hostname], *args, **kwargs) + +class Option (Parameter) : + """ + A generic 'option' parameter for a dhcpd.conf file + """ + + def __init__ (self, name, *args, **kwargs) : + """ + Formatted as a Satement with a name of "option ". + """ + + super(Option, self).__init__("option %s" % name, *args, **kwargs) + diff -r 46d36bc33086 -r 2156906bfbf1 pvl/config/dhcpd/model.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pvl/config/dhcpd/model.py Sun Jul 12 02:14:34 2009 +0300 @@ -0,0 +1,81 @@ +""" + Higher-level DHCP config structure model +""" + +import dhcp_conf as dhcpc + +class Config (dhcpc.ConfFile) : + """ + A full configuration file + """ + + def __init__ (self, name=dhcpc.ConfFile.DEFAULT_NAME, path=dhcpc.ConfFile.DEFAULT_PATH, + settings=None, options=None, shared_network=False, subnets=None, hosts=None, comment=None + ) : + """ + Create a full configuration file for the given settings: + + settings: a { name: value } mappping of general settings to set + options: a { opt_name: opt_value } mapping of options to set + shared_network: define the subnets as a shared network of the given name + subnets: an iterable of Subnet's to define + hosts: an iterable of Host's to define + + """ + + dhcpc.ConfFile.__init__(self, name, path, comment=comment) + + # define global settings + if settings : + self.add_params(dhcpc.Parameter(setting, value) for setting, value in settings.iteritems()) + + # define global options + if options : + self.add_params(dhcpc.Option(option, value) for option, value in options.iteritems()) + + # the shared-network section, or a series of subnets + if shared_network : + self.add_decl(dhcpc.SharedNetwork(shared_network, decls=subnets)) + + elif subnets : + self.add_decls(subnets) + + # hosts section + if hosts : + self.add_decls(hosts) + +class Subnet (dhcpc.Subnet) : + """ + A subnet declaration with a router, and optionally a dynamic address pool, and allow/deny unknown clients + """ + + def __init__ (self, subnet, router_idx=1, range=None, unknown_clients=None, comment=None) : + """ + @param subnet the addr.IP representing the subnet + @param router_idx the subnet[index] of the default gateway + @param range optional (from_idx, to_idx) to define a dhcp pool + @param unknown_clients optional 'allow'/'deny' to set a policy for unknown clients + """ + + # validate + if unknown_clients : + assert unknown_clients in ('allow', 'deny') + + super(Subnet, self).__init__(subnet, params=[ + dhcpc.Option("routers", subnet[router_idx]), + dhcpc.Parameter("range", subnet[range[0]], subnet[range[1]]) if range else None, + dhcpc.Parameter(unknown_clients, "unknown-clients") if unknown_clients else None, + ], comment=comment) + + +class Host (dhcpc.Host) : + """ + A host declaration with a hardware address and a IP address + """ + + def __init__ (self, hostname, mac_addr, ip_addr, comment=None) : + super(Host, self).__init__(hostname, params=[ + dhcpc.Parameter("hardware ethernet", mac_addr), + dhcpc.Parameter("fixed-address", ip_addr) + ], comment=comment) + diff -r 46d36bc33086 -r 2156906bfbf1 test/bind.py --- a/test/bind.py Sun Jul 12 02:14:08 2009 +0300 +++ b/test/bind.py Sun Jul 12 02:14:34 2009 +0300 @@ -3,8 +3,9 @@ Test bind_conf """ -import bind_conf as bindc -import test_conf, addr +from pvl.config.bind import zone +from pvl.config import addr +from . import conf import unittest