dhcp_conf.py
author Tero Marttila <terom@fixme.fi>
Thu, 02 Apr 2009 20:54:37 +0300
changeset 2 e66102ab7048
parent 1 2223ade4f259
child 3 ff98fa9b84ce
permissions -rw-r--r--
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)