--- a/addr.py Sun Jul 12 00:43:36 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-"""
- Used to define IP-address/subnet stuff
-"""
-
-import IPy
-
-class IP (IPy.IP, object) :
- """
- A literal IPv4 address
- """
-
- def __init__ (self, address) :
- """
- Parse the given literal IP address in "a.b.c.d" form
- """
-
- super(IP, self).__init__(address)
-
- def is_v4 (self) :
- """
- Returns True if the address is an IPv4 address
- """
-
- return self.version() == 4
-
- def is_v6 (self) :
- """
- Returns True if the address is an IPv6 address
- """
-
- return self.version() == 6
-
-class Network (IPy.IP, object) :
- """
- An IPv4 network (subnet)
- """
-
- def __init__ (self, prefix) :
- """
- Parse the given prefix in "a.b.c.d/l" form
- """
-
- super(Network, self).__init__(prefix)
-
-class MAC (object) :
- """
- A mac address
- """
-
- def __init__ (self, mac) :
- """
- Parse the given MAC address in "aa:bb:cc:dd:ee:ff" form
- """
-
- # XXX: validate
- self.mac = mac
-
- def __str__ (self) :
- return self.mac
-
--- a/bind.py Sun Jul 12 00:43:36 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)
--- a/bind_conf.py Sun Jul 12 00:43:36 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:
- ";" <comment>
- """
-
- 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)
-
--- a/conf.py Sun Jul 12 00:43:36 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,136 +0,0 @@
-"""
- Generic configuration file output
-"""
-
-import os, os.path, tempfile, shutil
-
-class ConfObject (object) :
- """
- An object that can be written to a ConfFile, as multiple lines of text.
- """
-
- def __init__ (self, comments=None) :
- """
- Initialize with the given list of comments. Comments that are None should be ignore.
- """
-
- # init the comments list
- self.comments = comments or []
-
- def add_comment (self, comment) :
- """
- Add a comment to be rendered in the output.
- """
-
- # add it
- self.comments.append(comment)
-
- def fmt_lines (self) :
- """
- Yield a series of lines to be output in the file
- """
-
- abstract
-
-class File (ConfObject) :
- """
- A single configuration file on the filesystem.
-
- Configuration files are themselves ConfObject's, although this must be implemented in the inheriting class.
- """
-
- def __init__ (self, name, path, backup_suffix='.bak', mode=0644) :
- """
- Initialize the config file, but don't open it yet
-
- @param name the human-readable friendly name for the config file
- @param path the full filesystem path for the file
- @param backup_suffix rename the old file by adding this suffix when replacing it
- @param mode the permission bits for the new file
- """
-
- self.name = name
- self.path = path
- self.backup_suffix = backup_suffix
- self.mode = mode
-
- def write_file (self, file) :
- """
- Write out this config's stuff into the given file
- """
-
- writer = Writer(file)
- writer.write_obj(self)
-
- def write (self) :
- """
- Write out a new config file with this config's stuff using the following procedure:
-
- * lock the real file
- * open a new temporary file, and write out the objects into that, closing it
- * move the real file out of the way
- * move the temporary file into the real file's place
- * unlock the real file
- """
-
- # XXX: how to aquire the lock?
- # XXX: this needs checking over
-
- # open the new temporary file
- # XXX: ensure fd is closed
- tmp_fd, tmp_path = tempfile.mkstemp(prefix=self.name)
-
- # ...as a file
- tmp_file = os.fdopen(tmp_fd, "w")
-
- # fix the permissions
- os.chmod(tmp_path, self.mode)
-
- # write it
- self.write_file(tmp_file)
-
- # close it
- tmp_file.close()
-
- # move the old file out of the way
- if os.path.exists(self.path) :
- os.rename(self.path, self.path + self.backup_suffix)
-
- # move the new file in
- shutil.move(tmp_path, self.path)
-
-class Writer (object) :
- """
- A conf.Writer is used to write out a new conf.File (as a temporary file)
- """
-
- def __init__ (self, file) :
- """
- @param file the temporary file object
- """
-
- self.file = file
-
- def write_line (self, line) :
- """
- Write a single line to the file
- """
-
- self.file.write("%s\n" % (line, ))
-
- def write_lines (self, lines) :
- """
- Write a series of lines into the file
- """
-
- for line in lines :
- self.write_line(line)
-
- def write_obj (self, obj) :
- """
- Write a single object to the file
- """
-
- # just write out all the lines
- self.write_lines(obj.fmt_lines())
-
--- a/data.py Sun Jul 12 00:43:36 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-"""
- Functions to load data from various sources
-"""
-
-import imp
-
-def load_py (name, path) :
- """
- Load a python file from the given filesystem path, returning the module itself.
-
- The "name" of the module must be given, it should be something sane and unique...
- """
-
- # just load it and return
- return imp.load_source(name, path)
-
--- a/dhcp.py Sun Jul 12 00:43:36 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)
-
--- a/dhcp_conf.py Sun Jul 12 00:43:36 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:
- <name> [ <arg> [ ... ] ] ";"
- """
-
- 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.
-
- <name> [ <args> [ ... ] ] {
- [ <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 <name> {
- [ 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 <subnet-number> netmask <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 <hostname> {
- [ 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 <name>".
- """
-
- super(Option, self).__init__("option %s" % name, *args, **kwargs)
-
--- a/host.py Sun Jul 12 00:43:36 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-"""
- Information about one physica host
-"""
-
-import dhcp
-import bind_conf as bindc
-
-class Interface (object) :
- """
- A physical interface for a host
- """
-
- def __init__ (self, mac_addr, name=None) :
- """
- @param name the short name of the interface (e.g. 'lan' or 'wlan'), or None for no suffix
- @param mac the physical-layer addr.MAC address
- """
-
- self.addr = mac_addr
- self.name = name
-
-class Host (object) :
- """
- A host has a single address/name, an owner, and multiple interfaces
- """
-
- def __init__ (self, hostname, address, interfaces) :
- """
- @param hostname the short hostname, without the domain name component
- @param address the addr.IP address
- @param interfaces a list of zero or more Interface objects
- """
-
- self.hostname = hostname
- self.address = address
- self.interfaces = interfaces
-
- def build_dhcp_hosts (self) :
- """
- Build and yield a series of dhcp_conf.Host objects for this host.
-
- If the host does not have any interfaces defined, this doesn't yield anything
- """
-
- # XXX: do we want to ensure that the host names are unique?
-
- for iface in self.interfaces :
- # the DHCP hostname
- name = "%s%s" % (self.hostname, ('-%s' % (iface.name)) if iface.name else '')
-
- # build it
- yield dhcp.Host(name, iface.addr, self.address)
-
- def build_bind_domain_records (self, origin) :
- """
- Build and yield one or more forward records (A/AAAA) for the host, with the given domain as the origin
- """
-
- yield bindc.A(self.hostname, self.address)
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/in/hosts.py Sun Jul 12 00:51:08 2009 +0300
@@ -0,0 +1,55 @@
+from addr import IP, Network
+from host import Interface, Host
+from dhcp import Subnet
+from bind import Settings as BindSettings
+from bind_conf import Interval, FQDN
+
+# BIND stuff
+domain = "paivola.fi"
+
+nameservers = [
+ FQDN("ranssi.paivola.fi"),
+ FQDN("misc1.idler.fi"),
+ FQDN("misc2.idler.fi"),
+ FQDN("srv.marttila.de"),
+ ]
+
+mailservers = [
+ FQDN("mail.paivola.fi"),
+ ]
+
+bind_settings = BindSettings(
+ ttl = 3601,
+ hostmaster = "hostmaster",
+ refresh = Interval(h=1),
+ retry = Interval(m=3),
+ expire = Interval(d=28),
+ minimum = Interval(60)
+ )
+
+# DHCP stuff
+dhcp_settings = {
+ 'default-lease-time': 43200,
+ 'max-lease-time': 86400,
+ 'authorative': None,
+}
+
+dhcp_options = {
+ 'domain-name-servers': IP('194.197.235.145'),
+}
+
+shared_network = 'PVL'
+subnets = [
+ Subnet(Network('194.197.235.0/24'), router_idx=1, range=(26, 70), unknown_clients='allow', comment="Public network"),
+ Subnet(Network('192.168.0.0/23'), router_idx=1, unknown_clients='deny', comment="Internal network"),
+]
+
+# general stuff
+hosts = [
+ Host('jumpgate', IP('194.197.235.1'), [ ]),
+ Host('mikk4', IP('194.197.235.72'), [
+ Interface('00:16:01:37:D1:D2'),
+ Interface('00:0F:B0:0A:EF:58'),
+ ]),
+]
+
--- a/main.py Sun Jul 12 00:43:36 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-#!/usr/bin/env python2.5
-
-import data, dhcp, bind_conf, bind
-
-import optparse, itertools
-
-def parse_args (argv) :
- """
- Parse the command-line arguments from the given argv list, returning a (options_struct, args_list) tuple,
- as per optparse.
- """
-
- usage = "Usage: %prog [options] data-file"
-
- # define our options
- parser = optparse.OptionParser(usage=usage)
- parser.add_option('--dhcpd-conf', dest='dhcpd_conf', metavar="PATH", help="path to dhcpd.conf", default='/etc/dhcp3/dhcpd.conf')
- parser.add_option('--bind-zone', dest='bind_zone', metavar="PATH", help="path to bind zone file", default=None)
- parser.add_option('--autoserial', dest='autoserial_path', metavar="PATH", help="path to autoserial file", default='autoserial')
-
- # parse them
- options, args = parser.parse_args(args=argv[1:])
-
- # parse the positional arguments
- data_file, = args
-
- # ok
- return options, (data_file, )
-
-def write_dhcp (options, settings) :
- """
- Write the DHCP config module using the data loaded from the given module
- """
-
- # build the config file
- config = dhcp.Config(path=options.dhcpd_conf,
- settings = settings.dhcp_settings,
- options = settings.dhcp_options,
- shared_network = settings.shared_network,
- subnets = settings.subnets,
- hosts = itertools.chain(*(host.build_dhcp_hosts() for host in settings.hosts)),
- )
-
- # write it out
- config.write()
-
-def write_bind (options, settings) :
- """
- Write a BIND config for a forward zone
- """
-
- assert options.bind_zone
-
- # load the serial
- autoserial = bind.AutoSerial(options.autoserial_path)
-
- # build the zone file
- zone = bind.Domain(domain=settings.domain, path=options.bind_zone,
- nameservers = settings.nameservers,
- mailservers = [((i+1)*10, label) for i, label in enumerate(settings.mailservers)],
- serial = autoserial.serial(),
- settings = settings.bind_settings,
- objs = itertools.chain(*[host.build_bind_domain_records(settings.domain) for host in settings.hosts]),
- )
-
- # write it out
- zone.write()
-
-def main (argv) :
- """
- Our app entry point, parse args, load data, write out the config files
- """
-
- # parse args
- options, (data_file, ) = parse_args(argv)
-
- # load the data
- data_module = data.load_py('pvl_hosts_data', data_file)
-
- # write out the config files
- write_dhcp(options, data_module)
- write_bind(options, data_module)
-
-if __name__ == '__main__' :
- from sys import argv
-
- main(argv)
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl-config Sun Jul 12 00:51:08 2009 +0300
@@ -0,0 +1,88 @@
+#!/usr/bin/env python2.5
+
+import data, dhcp, bind_conf, bind
+
+import optparse, itertools
+
+def parse_args (argv) :
+ """
+ Parse the command-line arguments from the given argv list, returning a (options_struct, args_list) tuple,
+ as per optparse.
+ """
+
+ usage = "Usage: %prog [options] data-file"
+
+ # define our options
+ parser = optparse.OptionParser(usage=usage)
+ parser.add_option('--dhcpd-conf', dest='dhcpd_conf', metavar="PATH", help="path to dhcpd.conf", default='/etc/dhcp3/dhcpd.conf')
+ parser.add_option('--bind-zone', dest='bind_zone', metavar="PATH", help="path to bind zone file", default=None)
+ parser.add_option('--autoserial', dest='autoserial_path', metavar="PATH", help="path to autoserial file", default='autoserial')
+
+ # parse them
+ options, args = parser.parse_args(args=argv[1:])
+
+ # parse the positional arguments
+ data_file, = args
+
+ # ok
+ return options, (data_file, )
+
+def write_dhcp (options, settings) :
+ """
+ Write the DHCP config module using the data loaded from the given module
+ """
+
+ # build the config file
+ config = dhcp.Config(path=options.dhcpd_conf,
+ settings = settings.dhcp_settings,
+ options = settings.dhcp_options,
+ shared_network = settings.shared_network,
+ subnets = settings.subnets,
+ hosts = itertools.chain(*(host.build_dhcp_hosts() for host in settings.hosts)),
+ )
+
+ # write it out
+ config.write()
+
+def write_bind (options, settings) :
+ """
+ Write a BIND config for a forward zone
+ """
+
+ assert options.bind_zone
+
+ # load the serial
+ autoserial = bind.AutoSerial(options.autoserial_path)
+
+ # build the zone file
+ zone = bind.Domain(domain=settings.domain, path=options.bind_zone,
+ nameservers = settings.nameservers,
+ mailservers = [((i+1)*10, label) for i, label in enumerate(settings.mailservers)],
+ serial = autoserial.serial(),
+ settings = settings.bind_settings,
+ objs = itertools.chain(*[host.build_bind_domain_records(settings.domain) for host in settings.hosts]),
+ )
+
+ # write it out
+ zone.write()
+
+def main (argv) :
+ """
+ Our app entry point, parse args, load data, write out the config files
+ """
+
+ # parse args
+ options, (data_file, ) = parse_args(argv)
+
+ # load the data
+ data_module = data.load_py('pvl_hosts_data', data_file)
+
+ # write out the config files
+ write_dhcp(options, data_module)
+ write_bind(options, data_module)
+
+if __name__ == '__main__' :
+ from sys import argv
+
+ main(argv)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/config/addr.py Sun Jul 12 00:51:08 2009 +0300
@@ -0,0 +1,60 @@
+"""
+ Used to define IP-address/subnet stuff
+"""
+
+import IPy
+
+class IP (IPy.IP, object) :
+ """
+ A literal IPv4 address
+ """
+
+ def __init__ (self, address) :
+ """
+ Parse the given literal IP address in "a.b.c.d" form
+ """
+
+ super(IP, self).__init__(address)
+
+ def is_v4 (self) :
+ """
+ Returns True if the address is an IPv4 address
+ """
+
+ return self.version() == 4
+
+ def is_v6 (self) :
+ """
+ Returns True if the address is an IPv6 address
+ """
+
+ return self.version() == 6
+
+class Network (IPy.IP, object) :
+ """
+ An IPv4 network (subnet)
+ """
+
+ def __init__ (self, prefix) :
+ """
+ Parse the given prefix in "a.b.c.d/l" form
+ """
+
+ super(Network, self).__init__(prefix)
+
+class MAC (object) :
+ """
+ A mac address
+ """
+
+ def __init__ (self, mac) :
+ """
+ Parse the given MAC address in "aa:bb:cc:dd:ee:ff" form
+ """
+
+ # XXX: validate
+ self.mac = mac
+
+ def __str__ (self) :
+ return self.mac
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/config/bind.py Sun Jul 12 00:51:08 2009 +0300
@@ -0,0 +1,187 @@
+"""
+ 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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/config/bind_conf.py Sun Jul 12 00:51:08 2009 +0300
@@ -0,0 +1,395 @@
+"""
+ 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:
+ ";" <comment>
+ """
+
+ 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)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/config/conf.py Sun Jul 12 00:51:08 2009 +0300
@@ -0,0 +1,136 @@
+"""
+ Generic configuration file output
+"""
+
+import os, os.path, tempfile, shutil
+
+class ConfObject (object) :
+ """
+ An object that can be written to a ConfFile, as multiple lines of text.
+ """
+
+ def __init__ (self, comments=None) :
+ """
+ Initialize with the given list of comments. Comments that are None should be ignore.
+ """
+
+ # init the comments list
+ self.comments = comments or []
+
+ def add_comment (self, comment) :
+ """
+ Add a comment to be rendered in the output.
+ """
+
+ # add it
+ self.comments.append(comment)
+
+ def fmt_lines (self) :
+ """
+ Yield a series of lines to be output in the file
+ """
+
+ abstract
+
+class File (ConfObject) :
+ """
+ A single configuration file on the filesystem.
+
+ Configuration files are themselves ConfObject's, although this must be implemented in the inheriting class.
+ """
+
+ def __init__ (self, name, path, backup_suffix='.bak', mode=0644) :
+ """
+ Initialize the config file, but don't open it yet
+
+ @param name the human-readable friendly name for the config file
+ @param path the full filesystem path for the file
+ @param backup_suffix rename the old file by adding this suffix when replacing it
+ @param mode the permission bits for the new file
+ """
+
+ self.name = name
+ self.path = path
+ self.backup_suffix = backup_suffix
+ self.mode = mode
+
+ def write_file (self, file) :
+ """
+ Write out this config's stuff into the given file
+ """
+
+ writer = Writer(file)
+ writer.write_obj(self)
+
+ def write (self) :
+ """
+ Write out a new config file with this config's stuff using the following procedure:
+
+ * lock the real file
+ * open a new temporary file, and write out the objects into that, closing it
+ * move the real file out of the way
+ * move the temporary file into the real file's place
+ * unlock the real file
+ """
+
+ # XXX: how to aquire the lock?
+ # XXX: this needs checking over
+
+ # open the new temporary file
+ # XXX: ensure fd is closed
+ tmp_fd, tmp_path = tempfile.mkstemp(prefix=self.name)
+
+ # ...as a file
+ tmp_file = os.fdopen(tmp_fd, "w")
+
+ # fix the permissions
+ os.chmod(tmp_path, self.mode)
+
+ # write it
+ self.write_file(tmp_file)
+
+ # close it
+ tmp_file.close()
+
+ # move the old file out of the way
+ if os.path.exists(self.path) :
+ os.rename(self.path, self.path + self.backup_suffix)
+
+ # move the new file in
+ shutil.move(tmp_path, self.path)
+
+class Writer (object) :
+ """
+ A conf.Writer is used to write out a new conf.File (as a temporary file)
+ """
+
+ def __init__ (self, file) :
+ """
+ @param file the temporary file object
+ """
+
+ self.file = file
+
+ def write_line (self, line) :
+ """
+ Write a single line to the file
+ """
+
+ self.file.write("%s\n" % (line, ))
+
+ def write_lines (self, lines) :
+ """
+ Write a series of lines into the file
+ """
+
+ for line in lines :
+ self.write_line(line)
+
+ def write_obj (self, obj) :
+ """
+ Write a single object to the file
+ """
+
+ # just write out all the lines
+ self.write_lines(obj.fmt_lines())
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/config/data.py Sun Jul 12 00:51:08 2009 +0300
@@ -0,0 +1,16 @@
+"""
+ Functions to load data from various sources
+"""
+
+import imp
+
+def load_py (name, path) :
+ """
+ Load a python file from the given filesystem path, returning the module itself.
+
+ The "name" of the module must be given, it should be something sane and unique...
+ """
+
+ # just load it and return
+ return imp.load_source(name, path)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/config/dhcp.py Sun Jul 12 00:51:08 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)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/config/dhcp_conf.py Sun Jul 12 00:51:08 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:
+ <name> [ <arg> [ ... ] ] ";"
+ """
+
+ 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.
+
+ <name> [ <args> [ ... ] ] {
+ [ <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 <name> {
+ [ 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 <subnet-number> netmask <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 <hostname> {
+ [ 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 <name>".
+ """
+
+ super(Option, self).__init__("option %s" % name, *args, **kwargs)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/config/host.py Sun Jul 12 00:51:08 2009 +0300
@@ -0,0 +1,60 @@
+"""
+ Information about one physica host
+"""
+
+import dhcp
+import bind_conf as bindc
+
+class Interface (object) :
+ """
+ A physical interface for a host
+ """
+
+ def __init__ (self, mac_addr, name=None) :
+ """
+ @param name the short name of the interface (e.g. 'lan' or 'wlan'), or None for no suffix
+ @param mac the physical-layer addr.MAC address
+ """
+
+ self.addr = mac_addr
+ self.name = name
+
+class Host (object) :
+ """
+ A host has a single address/name, an owner, and multiple interfaces
+ """
+
+ def __init__ (self, hostname, address, interfaces) :
+ """
+ @param hostname the short hostname, without the domain name component
+ @param address the addr.IP address
+ @param interfaces a list of zero or more Interface objects
+ """
+
+ self.hostname = hostname
+ self.address = address
+ self.interfaces = interfaces
+
+ def build_dhcp_hosts (self) :
+ """
+ Build and yield a series of dhcp_conf.Host objects for this host.
+
+ If the host does not have any interfaces defined, this doesn't yield anything
+ """
+
+ # XXX: do we want to ensure that the host names are unique?
+
+ for iface in self.interfaces :
+ # the DHCP hostname
+ name = "%s%s" % (self.hostname, ('-%s' % (iface.name)) if iface.name else '')
+
+ # build it
+ yield dhcp.Host(name, iface.addr, self.address)
+
+ def build_bind_domain_records (self, origin) :
+ """
+ Build and yield one or more forward records (A/AAAA) for the host, with the given domain as the origin
+ """
+
+ yield bindc.A(self.hostname, self.address)
+
--- a/settings/hosts.py Sun Jul 12 00:43:36 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-from addr import IP, Network
-from host import Interface, Host
-from dhcp import Subnet
-from bind import Settings as BindSettings
-from bind_conf import Interval, FQDN
-
-# BIND stuff
-domain = "paivola.fi"
-
-nameservers = [
- FQDN("ranssi.paivola.fi"),
- FQDN("misc1.idler.fi"),
- FQDN("misc2.idler.fi"),
- FQDN("srv.marttila.de"),
- ]
-
-mailservers = [
- FQDN("mail.paivola.fi"),
- ]
-
-bind_settings = BindSettings(
- ttl = 3601,
- hostmaster = "hostmaster",
- refresh = Interval(h=1),
- retry = Interval(m=3),
- expire = Interval(d=28),
- minimum = Interval(60)
- )
-
-# DHCP stuff
-dhcp_settings = {
- 'default-lease-time': 43200,
- 'max-lease-time': 86400,
- 'authorative': None,
-}
-
-dhcp_options = {
- 'domain-name-servers': IP('194.197.235.145'),
-}
-
-shared_network = 'PVL'
-subnets = [
- Subnet(Network('194.197.235.0/24'), router_idx=1, range=(26, 70), unknown_clients='allow', comment="Public network"),
- Subnet(Network('192.168.0.0/23'), router_idx=1, unknown_clients='deny', comment="Internal network"),
-]
-
-# general stuff
-hosts = [
- Host('jumpgate', IP('194.197.235.1'), [ ]),
- Host('mikk4', IP('194.197.235.72'), [
- Interface('00:16:01:37:D1:D2'),
- Interface('00:0F:B0:0A:EF:58'),
- ]),
-]
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/bind.py Sun Jul 12 00:51:08 2009 +0300
@@ -0,0 +1,70 @@
+#!/usr/bin/env python2.5
+"""
+ Test bind_conf
+"""
+
+import bind_conf as bindc
+import test_conf, addr
+
+import unittest
+
+class TestBINDConf (test_conf._TestConfBase) :
+ def test_comment (self) :
+ self.assert_obj(bindc.Comment("test comment 2"), [ "; test comment 2" ])
+
+ def assert_str (self, label, value) :
+ self.assertEqual(str(label), value)
+
+ def test_label (self) :
+ self.assert_str(bindc.Label("foo"), "foo")
+
+ def test_origin (self) :
+ self.assert_str(bindc.Origin(), "@")
+
+ def test_fqdn (self) :
+ self.assert_str(bindc.FQDN("foo.com"), "foo.com.")
+
+ def test_interval (self) :
+ self.assert_str(bindc.Interval(12), "12")
+ self.assert_str(bindc.Interval(12, 1), "12s1m")
+ self.assert_str(bindc.Interval(h=2, d=5), "2h5d")
+
+ def assert_rec (self, obj, lines) :
+ """
+ Does a whitespace-insensitive compare of the record's formatted output and the given lines
+ """
+
+ for obj_line, line in zip(obj.fmt_lines(), lines) :
+ obj_line = ' '.join(obj_line.split())
+ self.assertEqual(obj_line, line)
+
+ def test_record (self) :
+ self.assert_rec(bindc.ResourceRecord(None, 'A', addr.IP("1.2.3.4"), cls='TST', ttl=60), [ "60 TST A 1.2.3.4" ])
+ self.assert_rec(bindc.ResourceRecord('foo', 'CNAME', 'blaa'), [ "foo IN CNAME blaa" ])
+ self.assert_rec(bindc.ResourceRecord('bar', 'CNAME', bindc.FQDN('example.com')), [ "bar IN CNAME example.com." ])
+
+ def test_record_types (self) :
+ self.assert_rec(bindc.SOA(None, 'ns1', 'hostmaster', '2009040200', bindc.Interval(1), 2, 3, 4), [
+ "IN SOA ns1 hostmaster ( 2009040200 1 2 3 4 )"
+ ])
+ self.assert_rec(bindc.A('foo', addr.IP("1.2.3.4")), [ "foo IN A 1.2.3.4" ])
+ self.assert_rec(bindc.AAAA('foo2', addr.IP("2001::5")), [ "foo2 IN AAAA 2001::5" ])
+ self.assert_rec(bindc.CNAME('foo3', bindc.FQDN("foo.com")), [ "foo3 IN CNAME foo.com." ])
+ self.assert_rec(bindc.TXT('test4', "this is some text"), [ "test4 IN TXT \"this is some text\"" ])
+ self.assert_rec(bindc.MX(None, 10, "foo"), [ "IN MX 10 foo" ])
+ self.assert_rec(bindc.NS(None, "ns2"), [ "IN NS ns2" ])
+ self.assert_rec(bindc.PTR(addr.IP("1.2.3.4"), 'bar'), [ "4.3.2.1.in-addr.arpa. IN PTR bar" ])
+
+ def test_directive (self) :
+ self.assert_obj(bindc.Directive("TEST", "a", None, 2, comments=["hmm..."]), [
+ "; hmm...",
+ "$TEST a 2"
+ ])
+ self.assert_obj(bindc.OriginDirective(bindc.FQDN("foo.com")), [ "$ORIGIN foo.com." ])
+ self.assert_obj(bindc.TTLDirective(bindc.Interval(h=2)), [ "$TTL 2h" ])
+ self.assert_obj(bindc.IncludeDirective("db.foo.com"), [ "$INCLUDE db.foo.com" ])
+ self.assert_obj(bindc.GenerateDirective((1, 10), "dyn${0,2}", 'A', "1.2.3.$"), [ "$GENERATE 1-10 dyn${0,2} A 1.2.3.$" ])
+
+if __name__ == '__main__' :
+ unittest.main()
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/conf.py Sun Jul 12 00:51:08 2009 +0300
@@ -0,0 +1,16 @@
+"""
+ Test conf.py
+"""
+
+import unittest
+
+class _TestConfBase (unittest.TestCase) :
+ def assert_obj (self, obj, lines) :
+ """
+ Formats the given conf.Object and compares the output against the given lines
+ """
+
+ for obj_line, line in zip(obj.fmt_lines(), lines) :
+ self.assertEqual(obj_line, line)
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/dhcp.py Sun Jul 12 00:51:08 2009 +0300
@@ -0,0 +1,166 @@
+#!/usr/bin/env python2.5
+"""
+ Test dhcp_conf
+"""
+
+import dhcp_conf as dhcpc
+import test_conf, dhcp, addr
+
+import unittest
+
+class TestDHCPConf (test_conf._TestConfBase) :
+ def assert_stmt (self, stmt, line) :
+ """
+ Formats the given Statement, and compares the output against the given line.
+
+ Note that the dhcpc.Statement doesn't have a working fmt_lines implementation.
+ """
+
+ self.assertEqual(stmt._fmt_data(), line)
+
+ def test_comment (self) :
+ self.assert_obj(dhcpc.Comment("foo bar"), [ "# foo bar" ])
+
+ def test_section (self) :
+ self.assert_obj(dhcpc.Section(comment="test"), [ "# test" ])
+
+ self.assert_obj(dhcpc.Section(params=[
+ dhcpc.Parameter("param0"), None
+ ], comment="foo"), [
+ "# foo",
+ "param0;",
+ ])
+
+ def test_statement (self) :
+ self.assert_stmt(dhcpc.Statement("stmt0"), "stmt0")
+ self.assert_stmt(dhcpc.Statement("stmt1", [ "this", "that" ]), "stmt1 this, that")
+ self.assert_stmt(dhcpc.Statement("stmt2", dhcpc.Literal("...")), "stmt2 ...")
+ self.assert_stmt(dhcpc.Statement("stmt3", u"quux"), "stmt3 quux")
+ self.assert_stmt(dhcpc.Statement("stmt4", "bar"), "stmt4 bar")
+ self.assert_stmt(dhcpc.Statement("stmt5", 1), "stmt5 1")
+ self.assert_stmt(dhcpc.Statement("stmt6", 1, None, 2), "stmt6 1 2")
+
+ def test_literal (self) :
+ self.assert_obj(dhcpc.Literal("///"), [ "///" ])
+
+ def test_parameter (self) :
+ self.assert_obj(dhcpc.Parameter("param0", "this", 13, "that"), [ "param0 this 13 that;" ])
+ self.assert_obj(dhcpc.Parameter("param1", comment="testing"), [ "# testing", "param1;" ])
+
+ def test_declaration (self) :
+ self.assert_obj(dhcpc.Declaration("decl0", ["arg0", "arg1"], [
+ dhcpc.Parameter("param0"),
+ None
+ ], [
+ dhcpc.Declaration("decl0.0", params=[
+ dhcpc.Parameter("param0.0.1", "value")
+ ])
+ ], comment="foo"), [
+ "# foo",
+ "decl0 arg0 arg1 {",
+ "\tparam0;",
+ "\tdecl0.0 {",
+ "\t\tparam0.0.1 value;",
+ "\t}",
+ "}",
+ ])
+
+ def test_shared_network (self) :
+ self.assert_obj(dhcpc.SharedNetwork("net0", params=[
+ dhcpc.Parameter("param0")
+ ]), [
+ "shared-network net0 {",
+ "\tparam0;",
+ "}"
+ ])
+
+ def test_subnet (self) :
+ self.assert_obj(dhcpc.Subnet(addr.Network("194.197.235.0/24"), params=[
+ dhcpc.Parameter("param0")
+ ]), [
+ "subnet 194.197.235.0 netmask 255.255.255.0 {",
+ "\tparam0;",
+ "}"
+ ])
+
+ def test_group (self) :
+ self.assert_obj(dhcpc.Group(decls=[
+ dhcpc.Declaration("decl0.0", params=[
+ dhcpc.Parameter("param0.0.1", "value")
+ ])
+ ]), [
+ "group {",
+ "\tdecl0.0 {",
+ "\t\tparam0.0.1 value;",
+ "\t}",
+ "}"
+ ])
+
+ def test_host (self) :
+ self.assert_obj(dhcpc.Host("test-hostname", params=[
+ dhcpc.Parameter("param0")
+ ]), [
+ "host test-hostname {",
+ "\tparam0;",
+ "}"
+ ])
+
+ def test_option (self) :
+ self.assert_obj(dhcpc.Option("foo", "example.com"), [
+ "option foo example.com;",
+ ])
+
+class TestDHCP (_TestConfObj) :
+ def test_host (self) :
+ self.assert_obj(dhcp.Host("testhost", addr.MAC("12:34:56:78:90:ab"), addr.IP("1.2.3.4"), comment="foo"), [
+ "# foo",
+ "host testhost {",
+ "\thardware ethernet 12:34:56:78:90:ab;",
+ "\tfixed-address 1.2.3.4;",
+ "}"
+ ])
+
+ def test_subnet (self) :
+ self.assert_obj(dhcp.Subnet(addr.Network("1.2.3.0/24"), comment="bar"), [
+ "# bar",
+ "subnet 1.2.3.0 netmask 255.255.255.0 {",
+ "\toption routers 1.2.3.1;",
+ "}"
+ ])
+
+ self.assert_obj(dhcp.Subnet(addr.Network("1.2.3.0/24"), router_idx=10, range=(20, 30), unknown_clients='allow'), [
+ "subnet 1.2.3.0 netmask 255.255.255.0 {",
+ "\toption routers 1.2.3.10;",
+ "\trange 1.2.3.20 1.2.3.30;",
+ "\tallow unknown-clients;",
+ "}"
+ ])
+
+ def test_config (self) :
+ self.assert_obj(dhcp.Config(
+ settings = { 'foo-setting': 'someval' },
+ options = { 'bar-opt': ['one', 'two'] },
+ shared_network = "FOO-NET",
+ subnets = [
+ dhcp.Subnet(addr.Network("1.2.3.0/24"))
+ ],
+ hosts = [
+ dhcp.Host("testhost", addr.MAC("12:34:56:78:90:ab"), addr.IP("1.2.3.4"))
+ ],
+ ), [
+ "foo-setting someval;",
+ "option bar-opt one, two;",
+ "shared-network FOO-NET {",
+ "\tsubnet 1.2.3.0 netmask 255.255.255.0 {",
+ "\t\toption routers 1.2.3.1;",
+ "\t}",
+ "}",
+ "host testhost {",
+ "\thardware ethernet 12:34:56:78:90:ab;",
+ "\tfixed-address 1.2.3.4;",
+ "}"
+ ])
+
+if __name__ == '__main__' :
+ unittest.main()
+
--- a/test_bind.py Sun Jul 12 00:43:36 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-#!/usr/bin/env python2.5
-"""
- Test bind_conf
-"""
-
-import bind_conf as bindc
-import test_conf, addr
-
-import unittest
-
-class TestBINDConf (test_conf._TestConfBase) :
- def test_comment (self) :
- self.assert_obj(bindc.Comment("test comment 2"), [ "; test comment 2" ])
-
- def assert_str (self, label, value) :
- self.assertEqual(str(label), value)
-
- def test_label (self) :
- self.assert_str(bindc.Label("foo"), "foo")
-
- def test_origin (self) :
- self.assert_str(bindc.Origin(), "@")
-
- def test_fqdn (self) :
- self.assert_str(bindc.FQDN("foo.com"), "foo.com.")
-
- def test_interval (self) :
- self.assert_str(bindc.Interval(12), "12")
- self.assert_str(bindc.Interval(12, 1), "12s1m")
- self.assert_str(bindc.Interval(h=2, d=5), "2h5d")
-
- def assert_rec (self, obj, lines) :
- """
- Does a whitespace-insensitive compare of the record's formatted output and the given lines
- """
-
- for obj_line, line in zip(obj.fmt_lines(), lines) :
- obj_line = ' '.join(obj_line.split())
- self.assertEqual(obj_line, line)
-
- def test_record (self) :
- self.assert_rec(bindc.ResourceRecord(None, 'A', addr.IP("1.2.3.4"), cls='TST', ttl=60), [ "60 TST A 1.2.3.4" ])
- self.assert_rec(bindc.ResourceRecord('foo', 'CNAME', 'blaa'), [ "foo IN CNAME blaa" ])
- self.assert_rec(bindc.ResourceRecord('bar', 'CNAME', bindc.FQDN('example.com')), [ "bar IN CNAME example.com." ])
-
- def test_record_types (self) :
- self.assert_rec(bindc.SOA(None, 'ns1', 'hostmaster', '2009040200', bindc.Interval(1), 2, 3, 4), [
- "IN SOA ns1 hostmaster ( 2009040200 1 2 3 4 )"
- ])
- self.assert_rec(bindc.A('foo', addr.IP("1.2.3.4")), [ "foo IN A 1.2.3.4" ])
- self.assert_rec(bindc.AAAA('foo2', addr.IP("2001::5")), [ "foo2 IN AAAA 2001::5" ])
- self.assert_rec(bindc.CNAME('foo3', bindc.FQDN("foo.com")), [ "foo3 IN CNAME foo.com." ])
- self.assert_rec(bindc.TXT('test4', "this is some text"), [ "test4 IN TXT \"this is some text\"" ])
- self.assert_rec(bindc.MX(None, 10, "foo"), [ "IN MX 10 foo" ])
- self.assert_rec(bindc.NS(None, "ns2"), [ "IN NS ns2" ])
- self.assert_rec(bindc.PTR(addr.IP("1.2.3.4"), 'bar'), [ "4.3.2.1.in-addr.arpa. IN PTR bar" ])
-
- def test_directive (self) :
- self.assert_obj(bindc.Directive("TEST", "a", None, 2, comments=["hmm..."]), [
- "; hmm...",
- "$TEST a 2"
- ])
- self.assert_obj(bindc.OriginDirective(bindc.FQDN("foo.com")), [ "$ORIGIN foo.com." ])
- self.assert_obj(bindc.TTLDirective(bindc.Interval(h=2)), [ "$TTL 2h" ])
- self.assert_obj(bindc.IncludeDirective("db.foo.com"), [ "$INCLUDE db.foo.com" ])
- self.assert_obj(bindc.GenerateDirective((1, 10), "dyn${0,2}", 'A', "1.2.3.$"), [ "$GENERATE 1-10 dyn${0,2} A 1.2.3.$" ])
-
-if __name__ == '__main__' :
- unittest.main()
-
--- a/test_conf.py Sun Jul 12 00:43:36 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-"""
- Test conf.py
-"""
-
-import unittest
-
-class _TestConfBase (unittest.TestCase) :
- def assert_obj (self, obj, lines) :
- """
- Formats the given conf.Object and compares the output against the given lines
- """
-
- for obj_line, line in zip(obj.fmt_lines(), lines) :
- self.assertEqual(obj_line, line)
-
-
--- a/test_dhcp.py Sun Jul 12 00:43:36 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +0,0 @@
-#!/usr/bin/env python2.5
-"""
- Test dhcp_conf
-"""
-
-import dhcp_conf as dhcpc
-import test_conf, dhcp, addr
-
-import unittest
-
-class TestDHCPConf (test_conf._TestConfBase) :
- def assert_stmt (self, stmt, line) :
- """
- Formats the given Statement, and compares the output against the given line.
-
- Note that the dhcpc.Statement doesn't have a working fmt_lines implementation.
- """
-
- self.assertEqual(stmt._fmt_data(), line)
-
- def test_comment (self) :
- self.assert_obj(dhcpc.Comment("foo bar"), [ "# foo bar" ])
-
- def test_section (self) :
- self.assert_obj(dhcpc.Section(comment="test"), [ "# test" ])
-
- self.assert_obj(dhcpc.Section(params=[
- dhcpc.Parameter("param0"), None
- ], comment="foo"), [
- "# foo",
- "param0;",
- ])
-
- def test_statement (self) :
- self.assert_stmt(dhcpc.Statement("stmt0"), "stmt0")
- self.assert_stmt(dhcpc.Statement("stmt1", [ "this", "that" ]), "stmt1 this, that")
- self.assert_stmt(dhcpc.Statement("stmt2", dhcpc.Literal("...")), "stmt2 ...")
- self.assert_stmt(dhcpc.Statement("stmt3", u"quux"), "stmt3 quux")
- self.assert_stmt(dhcpc.Statement("stmt4", "bar"), "stmt4 bar")
- self.assert_stmt(dhcpc.Statement("stmt5", 1), "stmt5 1")
- self.assert_stmt(dhcpc.Statement("stmt6", 1, None, 2), "stmt6 1 2")
-
- def test_literal (self) :
- self.assert_obj(dhcpc.Literal("///"), [ "///" ])
-
- def test_parameter (self) :
- self.assert_obj(dhcpc.Parameter("param0", "this", 13, "that"), [ "param0 this 13 that;" ])
- self.assert_obj(dhcpc.Parameter("param1", comment="testing"), [ "# testing", "param1;" ])
-
- def test_declaration (self) :
- self.assert_obj(dhcpc.Declaration("decl0", ["arg0", "arg1"], [
- dhcpc.Parameter("param0"),
- None
- ], [
- dhcpc.Declaration("decl0.0", params=[
- dhcpc.Parameter("param0.0.1", "value")
- ])
- ], comment="foo"), [
- "# foo",
- "decl0 arg0 arg1 {",
- "\tparam0;",
- "\tdecl0.0 {",
- "\t\tparam0.0.1 value;",
- "\t}",
- "}",
- ])
-
- def test_shared_network (self) :
- self.assert_obj(dhcpc.SharedNetwork("net0", params=[
- dhcpc.Parameter("param0")
- ]), [
- "shared-network net0 {",
- "\tparam0;",
- "}"
- ])
-
- def test_subnet (self) :
- self.assert_obj(dhcpc.Subnet(addr.Network("194.197.235.0/24"), params=[
- dhcpc.Parameter("param0")
- ]), [
- "subnet 194.197.235.0 netmask 255.255.255.0 {",
- "\tparam0;",
- "}"
- ])
-
- def test_group (self) :
- self.assert_obj(dhcpc.Group(decls=[
- dhcpc.Declaration("decl0.0", params=[
- dhcpc.Parameter("param0.0.1", "value")
- ])
- ]), [
- "group {",
- "\tdecl0.0 {",
- "\t\tparam0.0.1 value;",
- "\t}",
- "}"
- ])
-
- def test_host (self) :
- self.assert_obj(dhcpc.Host("test-hostname", params=[
- dhcpc.Parameter("param0")
- ]), [
- "host test-hostname {",
- "\tparam0;",
- "}"
- ])
-
- def test_option (self) :
- self.assert_obj(dhcpc.Option("foo", "example.com"), [
- "option foo example.com;",
- ])
-
-class TestDHCP (_TestConfObj) :
- def test_host (self) :
- self.assert_obj(dhcp.Host("testhost", addr.MAC("12:34:56:78:90:ab"), addr.IP("1.2.3.4"), comment="foo"), [
- "# foo",
- "host testhost {",
- "\thardware ethernet 12:34:56:78:90:ab;",
- "\tfixed-address 1.2.3.4;",
- "}"
- ])
-
- def test_subnet (self) :
- self.assert_obj(dhcp.Subnet(addr.Network("1.2.3.0/24"), comment="bar"), [
- "# bar",
- "subnet 1.2.3.0 netmask 255.255.255.0 {",
- "\toption routers 1.2.3.1;",
- "}"
- ])
-
- self.assert_obj(dhcp.Subnet(addr.Network("1.2.3.0/24"), router_idx=10, range=(20, 30), unknown_clients='allow'), [
- "subnet 1.2.3.0 netmask 255.255.255.0 {",
- "\toption routers 1.2.3.10;",
- "\trange 1.2.3.20 1.2.3.30;",
- "\tallow unknown-clients;",
- "}"
- ])
-
- def test_config (self) :
- self.assert_obj(dhcp.Config(
- settings = { 'foo-setting': 'someval' },
- options = { 'bar-opt': ['one', 'two'] },
- shared_network = "FOO-NET",
- subnets = [
- dhcp.Subnet(addr.Network("1.2.3.0/24"))
- ],
- hosts = [
- dhcp.Host("testhost", addr.MAC("12:34:56:78:90:ab"), addr.IP("1.2.3.4"))
- ],
- ), [
- "foo-setting someval;",
- "option bar-opt one, two;",
- "shared-network FOO-NET {",
- "\tsubnet 1.2.3.0 netmask 255.255.255.0 {",
- "\t\toption routers 1.2.3.1;",
- "\t}",
- "}",
- "host testhost {",
- "\thardware ethernet 12:34:56:78:90:ab;",
- "\tfixed-address 1.2.3.4;",
- "}"
- ])
-
-if __name__ == '__main__' :
- unittest.main()
-