fix up data.load_py, and make conf.File be a ConfObject itself - implement this for dhcp_conf
"""
Configuration file output for the ISC DHCP server
"""
import conf
import itertools
class Comment (conf.ConfObject) :
"""
A comment, is, well, a comment :)
Currently, comments are only one line, and look like the following:
"#" <comment>
"""
def __init__ (self, comment) :
"""
@param comment the comment string
"""
self.comment = comment
def fmt_lines (self) :
"""
Yield a single line with the comment
"""
yield "# %s" % (self.comment, )
class Section (conf.ConfObject) :
"""
A section holds a list of params and a list of decls
"""
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
"""
self.params = params or []
self.decls = decls or []
self.comment = comment
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_comment (self) :
"""
Format our comment line
"""
return "# %s" % (self.comment, )
def fmt_lines (self) :
"""
Format all of our params and decls, in that order
"""
# comment?
if self.comment :
yield self._fmt_comment()
# 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 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) :
"""
Initialize the dhcpd config file, but don't open it yet.
"""
conf.File.__init__(self, name, path)
Section.__init__(self, params, decls)
class Statement (conf.ConfObject) :
"""
A statement is a single line in the config file
"""
def __init__ (self, name, *args) :
"""
Arguments given as None will be ignored.
"""
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
"""
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) :
"""
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)
def fmt_lines (self) :
"""
Yields a header line, a series of indented body lines, and the footer 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, params=[], decls=[]) :
"""
@param name the name of the shared-subnet
@param params optional parameters
@param decls the iterable of subnets or other declarations in the shared network
"""
super(SharedNetwork, self).__init__("shared-network", [name], params, decls)
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, params=None, decls=None) :
"""
@param network the addr.Network for the subnet
@param params optional parameters
@param decls optional decls, e.g. subnets
"""
super(Subnet, self).__init__("subnet", [network.net(), "netmask", network.netmask()], params, decls)
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, params=None, decls=None) :
super(Group, self).__init__("group", [], params, decls)
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, params=None, decls=None) :
super(Host, self).__init__("host", [hostname], params, decls)
class Option (Parameter) :
"""
A generic 'option' parameter for a dhcpd.conf file
"""
def __init__ (self, name, *args) :
"""
Formatted as a Satement with a name of "option <name>".
"""
super(Option, self).__init__("option %s" % name, *args)