pvl/config/dhcpd/conf.py
changeset 9 2156906bfbf1
parent 7 0f9cae2d7147
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/config/dhcpd/conf.py	Sun Jul 12 02:14:34 2009 +0300
@@ -0,0 +1,341 @@
+"""
+    Configuration file output for the ISC DHCP server
+"""
+
+import conf
+
+import itertools
+
+class Object (conf.ConfObject) :
+    """
+        Our version of ConfObject
+    """
+    
+    def _fmt_comments (self) :
+        """
+            Format our comment lines
+        """
+        
+        for comment in self.comments :
+            if comment is not None :
+                yield "# %s" % (comment, )
+
+
+class Comment (Object) :
+    """
+        A comment, is, well, a comment :)
+    """
+
+    def __init__ (self, comment) :
+        """
+            @param comment the comment string
+        """
+
+        Object.__init__(self, [comment])
+    
+    def fmt_lines (self) :
+        """
+            Yield a single line with the comment
+        """
+
+        return self._fmt_comments()
+
+class _Section (Object) :
+    """
+        Base implementation of Section, but doesn't format comments in output (inheriting class can define how that happens)
+    """
+
+    def __init__ (self, params=None, decls=None, comment=None) :
+        """
+            If params/decls are given, those are the used as the initial contents of this section
+
+            If a comment is given, then it will be formatted before the section's stuff
+        """
+        
+        Object.__init__(self, [comment])
+
+        self.params = params or []
+        self.decls = decls or []
+
+    def add_param (self, param) :
+        """
+            Add the given Parameter to the end of this section's params
+        """
+
+        self.params.append(param)
+
+    def add_params (self, params) :
+        for param in params :
+            self.add_param(param)
+
+    def add_decl (self, decl) :
+        """
+            Add the given Declaration to the end of this section's decls
+        """
+
+        self.decls.append(decl)
+
+    def add_decls (self, decls) :
+        for decl in decls :
+            self.add_decl(decl)
+
+    def fmt_lines (self) :
+        """
+            Format all of our params and decls, in that order
+        """
+
+        # then output each content line
+        for stmt in itertools.chain(self.params, self.decls) :
+            # skip Nones
+            if stmt is None :
+                continue
+
+            for line in stmt.fmt_lines() :
+                yield line
+
+class Section (_Section) :
+    """
+        A section holds a list of params and a list of decls, plus some comments at the beginning of the section
+    """
+    
+    def fmt_lines (self) :
+        """
+            Format all of our comments, and then super
+        """
+
+        # comments
+        for line in self._fmt_comments() :
+            yield line
+
+        # section stuff
+        for line in _Section.fmt_lines(self) :
+            yield line
+
+class ConfFile (Section, conf.File) :
+    DEFAULT_NAME = "dhcpd.conf"
+    DEFAULT_PATH = "/etc/dhcp3/dhcpd.conf"
+
+    def __init__ (self, name=DEFAULT_NAME, path=DEFAULT_PATH, params=None, decls=None, comment=None) :
+        """
+            Initialize the dhcpd config file, but don't open it yet.
+        """
+        
+        conf.File.__init__(self, name, path)
+        Section.__init__(self, params, decls, comment)
+
+class Statement (Object) :
+    """
+        A statement is a single line in the config file
+    """
+
+    def __init__ (self, name, *args, **kwargs) :
+        """
+            Arguments given as None will be ignored.
+
+            A comment can be given as a keyword argument
+        """
+
+        if kwargs : assert len(kwargs) == 1 and 'comment' in kwargs
+
+        Object.__init__(self, [kwargs.get('comment')])
+
+        self.name = name
+        self.args = [arg for arg in args if arg is not None]
+    
+    def _fmt_arg (self, arg) :
+        """
+            Formats a arg for use in output, the following types are supported:
+
+                list/tuple/iter:    results in a comma-and-space separated list of formatted values
+                unicode:            results in an encoded str
+                str:                results in the string itself, quoted if needed
+                other:              attempt to convert to a str, and then format that
+        """
+        
+        # format lists specially
+        # XXX: iterators?
+        if isinstance(arg, (list, tuple)) :
+            # recurse as a comma-and-space separated list
+            return ', '.join(self._fmt_arg(a) for a in arg)
+
+        elif isinstance(arg, Literal) :
+            # use what it specifies
+            return arg.fmt_arg()
+
+        elif isinstance(arg, unicode) :
+            # recurse with the str version
+            # XXX: what encoding to use?
+            return self._fmt_arg(arg.encode('utf8'))
+
+        elif isinstance(arg, str) :
+            # XXX: quoting
+            return arg
+        
+        else :
+            # try and use it as a string
+            return self._fmt_arg(str(arg))
+    
+    def _fmt_data (self) :
+        """
+            Formats the statement name/params as a single line, ignoring None
+        """
+
+        return "%s%s" % (self.name, (' ' + ' '.join(self._fmt_arg(a) for a in self.args)) if self.args else '')
+
+class Literal (Statement) :
+    """
+        A literal is something that goes into the config file as-is, with no formatting or escaping applied.
+    """
+
+    def __init__ (self, literal) :
+        self.literal = literal
+    
+    def fmt_arg (self) :
+        return self.literal
+
+    def fmt_lines (self) :
+        yield self.literal
+
+class Parameter (Statement) :
+    """
+        A parameter is a single statement that configures the behaviour of something.
+
+        Parameters have a name, and optionally, a number of arguments, and are formatted as statements terminated with
+        a semicolon. For convenience, params/decls that are None are ignored.
+            
+        The parameter will be formatted like this:
+            <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)
+