terom@0: """ terom@0: Configuration file output for the ISC DHCP server terom@0: """ terom@0: terom@0: import conf terom@0: terom@0: import itertools terom@0: terom@3: class Object (conf.ConfObject) : terom@3: """ terom@3: Our version of ConfObject terom@3: """ terom@3: terom@3: def _fmt_comments (self) : terom@3: """ terom@3: Format our comment lines terom@3: """ terom@3: terom@3: for comment in self.comments : terom@3: if comment is not None : terom@3: yield "# %s" % (comment, ) terom@3: terom@3: terom@3: class Comment (Object) : terom@2: """ terom@2: A comment, is, well, a comment :) terom@2: """ terom@2: terom@2: def __init__ (self, comment) : terom@2: """ terom@2: @param comment the comment string terom@2: """ terom@2: terom@3: Object.__init__(self, [comment]) terom@2: terom@2: def fmt_lines (self) : terom@2: """ terom@2: Yield a single line with the comment terom@2: """ terom@2: terom@3: return self._fmt_comments() terom@2: terom@3: class _Section (Object) : terom@1: """ terom@3: Base implementation of Section, but doesn't format comments in output (inheriting class can define how that happens) terom@1: """ terom@1: terom@2: def __init__ (self, params=None, decls=None, comment=None) : terom@0: """ terom@1: If params/decls are given, those are the used as the initial contents of this section terom@2: terom@2: If a comment is given, then it will be formatted before the section's stuff terom@1: """ terom@3: terom@3: Object.__init__(self, [comment]) terom@0: terom@1: self.params = params or [] terom@1: self.decls = decls or [] terom@1: terom@1: def add_param (self, param) : terom@1: """ terom@1: Add the given Parameter to the end of this section's params terom@1: """ terom@1: terom@1: self.params.append(param) terom@1: terom@1: def add_params (self, params) : terom@1: for param in params : terom@1: self.add_param(param) terom@1: terom@1: def add_decl (self, decl) : terom@1: """ terom@1: Add the given Declaration to the end of this section's decls terom@1: """ terom@1: terom@1: self.decls.append(decl) terom@1: terom@1: def add_decls (self, decls) : terom@1: for decl in decls : terom@1: self.add_decl(decl) terom@1: terom@1: def fmt_lines (self) : terom@1: """ terom@1: Format all of our params and decls, in that order terom@1: """ terom@1: terom@1: # then output each content line terom@1: for stmt in itertools.chain(self.params, self.decls) : terom@1: # skip Nones terom@1: if stmt is None : terom@1: continue terom@1: terom@1: for line in stmt.fmt_lines() : terom@1: yield line terom@1: terom@3: class Section (_Section) : terom@3: """ terom@3: A section holds a list of params and a list of decls, plus some comments at the beginning of the section terom@3: """ terom@3: terom@3: def fmt_lines (self) : terom@3: """ terom@3: Format all of our comments, and then super terom@3: """ terom@3: terom@3: # comments terom@3: for line in self._fmt_comments() : terom@3: yield line terom@3: terom@3: # section stuff terom@3: for line in _Section.fmt_lines(self) : terom@3: yield line terom@3: terom@1: class ConfFile (Section, conf.File) : terom@1: DEFAULT_NAME = "dhcpd.conf" terom@1: DEFAULT_PATH = "/etc/dhcp3/dhcpd.conf" terom@1: terom@4: def __init__ (self, name=DEFAULT_NAME, path=DEFAULT_PATH, params=None, decls=None, comment=None) : terom@1: """ terom@1: Initialize the dhcpd config file, but don't open it yet. terom@0: """ terom@0: terom@1: conf.File.__init__(self, name, path) terom@4: Section.__init__(self, params, decls, comment) terom@0: terom@3: class Statement (Object) : terom@0: """ terom@1: A statement is a single line in the config file terom@0: """ terom@0: terom@3: def __init__ (self, name, *args, **kwargs) : terom@0: """ terom@1: Arguments given as None will be ignored. terom@3: terom@3: A comment can be given as a keyword argument terom@0: """ terom@0: terom@3: if kwargs : assert len(kwargs) == 1 and 'comment' in kwargs terom@3: terom@3: Object.__init__(self, [kwargs.get('comment')]) terom@3: terom@0: self.name = name terom@2: self.args = [arg for arg in args if arg is not None] terom@0: terom@0: def _fmt_arg (self, arg) : terom@0: """ terom@0: Formats a arg for use in output, the following types are supported: terom@0: terom@0: list/tuple/iter: results in a comma-and-space separated list of formatted values terom@0: unicode: results in an encoded str terom@0: str: results in the string itself, quoted if needed terom@0: other: attempt to convert to a str, and then format that terom@0: """ terom@0: terom@0: # format lists specially terom@0: # XXX: iterators? terom@0: if isinstance(arg, (list, tuple)) : terom@0: # recurse as a comma-and-space separated list terom@0: return ', '.join(self._fmt_arg(a) for a in arg) terom@0: terom@0: elif isinstance(arg, Literal) : terom@0: # use what it specifies terom@0: return arg.fmt_arg() terom@0: terom@0: elif isinstance(arg, unicode) : terom@0: # recurse with the str version terom@0: # XXX: what encoding to use? terom@0: return self._fmt_arg(arg.encode('utf8')) terom@0: terom@0: elif isinstance(arg, str) : terom@0: # XXX: quoting terom@0: return arg terom@0: terom@0: else : terom@0: # try and use it as a string terom@0: return self._fmt_arg(str(arg)) terom@0: terom@0: def _fmt_data (self) : terom@0: """ terom@1: Formats the statement name/params as a single line, ignoring None terom@0: """ terom@0: terom@2: return "%s%s" % (self.name, (' ' + ' '.join(self._fmt_arg(a) for a in self.args)) if self.args else '') terom@0: terom@0: class Literal (Statement) : terom@0: """ terom@0: A literal is something that goes into the config file as-is, with no formatting or escaping applied. terom@0: """ terom@0: terom@0: def __init__ (self, literal) : terom@0: self.literal = literal terom@0: terom@0: def fmt_arg (self) : terom@0: return self.literal terom@0: terom@0: def fmt_lines (self) : terom@0: yield self.literal terom@0: terom@0: class Parameter (Statement) : terom@0: """ terom@0: A parameter is a single statement that configures the behaviour of something. terom@0: terom@0: Parameters have a name, and optionally, a number of arguments, and are formatted as statements terminated with terom@1: a semicolon. For convenience, params/decls that are None are ignored. terom@2: terom@2: The parameter will be formatted like this: terom@2: [ [ ... ] ] ";" terom@0: """ terom@0: terom@0: def fmt_lines (self) : terom@0: """ terom@0: Yields a single ;-terminated line terom@0: """ terom@3: terom@3: # comments terom@3: for line in self._fmt_comments() : terom@3: yield line terom@3: terom@3: # the line terom@0: yield "%s;" % self._fmt_data() terom@0: terom@3: class Declaration (_Section, Statement) : terom@0: """ terom@1: A declaration begins like a statement (with name and args), but then contains a curly-braces-delimited block terom@1: that acts like a Section. terom@0: terom@0: [ [ ... ] ] { terom@1: [
] terom@0: } terom@0: terom@0: """ terom@0: terom@3: def __init__ (self, name, args=[], params=None, decls=None, comment=None) : terom@0: """ terom@0: The name/args will be formatted as in Statement, but params should be an iterable of Parameters, and decls terom@0: an iterable of Declarations. terom@0: """ terom@0: terom@0: # init the statement bit terom@3: _Section.__init__(self, params, decls) terom@3: Statement.__init__(self, name, *args, **dict(comment=comment)) terom@0: terom@0: def fmt_lines (self) : terom@0: """ terom@0: Yields a header line, a series of indented body lines, and the footer line terom@0: """ terom@0: terom@3: # comments terom@3: for line in self._fmt_comments() : terom@3: yield line terom@3: terom@0: # the header to open the block terom@0: yield "%s {" % self._fmt_data() terom@0: terom@1: # then output the section stuff, indented terom@3: for line in _Section.fmt_lines(self) : terom@1: yield "\t%s" % line terom@1: terom@0: # and then close the block terom@0: yield "}" terom@0: terom@0: class SharedNetwork (Declaration) : terom@0: """ terom@0: A shared-network declaration is used to define a set of subnets that share the same physical network, terom@0: optionally with some shared params. terom@0: terom@0: shared-network { terom@0: [ parameters ] terom@0: [ declarations ] terom@0: } terom@0: """ terom@0: terom@4: def __init__ (self, name, *args, **kwargs) : terom@0: """ terom@0: @param name the name of the shared-subnet terom@0: """ terom@0: terom@4: super(SharedNetwork, self).__init__("shared-network", [name], *args, **kwargs) terom@0: terom@0: class Subnet (Declaration) : terom@0: """ terom@0: A subnet is used to provide the information about a subnet required to identify whether or not an IP address is terom@0: on that subnet, and may also be used to specify parameters/declarations for that subnet. terom@0: terom@0: subnet netmask { terom@0: [ parameters ] terom@0: [ declarations ] terom@0: } terom@0: """ terom@0: terom@4: def __init__ (self, network, *args, **kwargs) : terom@0: """ terom@0: @param network the addr.Network for the subnet terom@0: """ terom@0: terom@4: super(Subnet, self).__init__("subnet", [network.net(), "netmask", network.netmask()], *args, **kwargs) terom@0: terom@0: class Group (Declaration) : terom@0: """ terom@0: A group is simply used to apply a set of parameters to a set of declarations. terom@0: terom@0: group { terom@0: [ parameters ] terom@0: [ declarations ] terom@0: } terom@0: """ terom@0: terom@4: def __init__ (self, *args, **kwargs) : terom@4: super(Group, self).__init__("group", [], *args, **kwargs) terom@0: terom@0: terom@0: class Host (Declaration) : terom@0: """ terom@0: A host is used to match a request against specific host, and then apply settings for that host. terom@0: terom@0: The "hostname" is the DHCP name to identify the host. terom@0: terom@0: If no dhcp-client-identifier option is specified in the parameters, then the host is matched using the terom@0: "hardware" parameter. terom@0: terom@0: host { terom@0: [ parameters ] terom@0: [ declarations ] terom@0: } terom@0: """ terom@0: terom@4: def __init__ (self, hostname, *args, **kwargs) : terom@4: super(Host, self).__init__("host", [hostname], *args, **kwargs) terom@0: terom@0: class Option (Parameter) : terom@0: """ terom@0: A generic 'option' parameter for a dhcpd.conf file terom@0: """ terom@0: terom@4: def __init__ (self, name, *args, **kwargs) : terom@0: """ terom@0: Formatted as a Satement with a name of "option ". terom@0: """ terom@0: terom@4: super(Option, self).__init__("option %s" % name, *args, **kwargs) terom@0: