pvl/config/dhcp_conf.py
changeset 9 2156906bfbf1
parent 8 46d36bc33086
child 10 65d91f6a2d2a
equal deleted inserted replaced
8:46d36bc33086 9:2156906bfbf1
     1 """
       
     2     Configuration file output for the ISC DHCP server
       
     3 """
       
     4 
       
     5 import conf
       
     6 
       
     7 import itertools
       
     8 
       
     9 class Object (conf.ConfObject) :
       
    10     """
       
    11         Our version of ConfObject
       
    12     """
       
    13     
       
    14     def _fmt_comments (self) :
       
    15         """
       
    16             Format our comment lines
       
    17         """
       
    18         
       
    19         for comment in self.comments :
       
    20             if comment is not None :
       
    21                 yield "# %s" % (comment, )
       
    22 
       
    23 
       
    24 class Comment (Object) :
       
    25     """
       
    26         A comment, is, well, a comment :)
       
    27     """
       
    28 
       
    29     def __init__ (self, comment) :
       
    30         """
       
    31             @param comment the comment string
       
    32         """
       
    33 
       
    34         Object.__init__(self, [comment])
       
    35     
       
    36     def fmt_lines (self) :
       
    37         """
       
    38             Yield a single line with the comment
       
    39         """
       
    40 
       
    41         return self._fmt_comments()
       
    42 
       
    43 class _Section (Object) :
       
    44     """
       
    45         Base implementation of Section, but doesn't format comments in output (inheriting class can define how that happens)
       
    46     """
       
    47 
       
    48     def __init__ (self, params=None, decls=None, comment=None) :
       
    49         """
       
    50             If params/decls are given, those are the used as the initial contents of this section
       
    51 
       
    52             If a comment is given, then it will be formatted before the section's stuff
       
    53         """
       
    54         
       
    55         Object.__init__(self, [comment])
       
    56 
       
    57         self.params = params or []
       
    58         self.decls = decls or []
       
    59 
       
    60     def add_param (self, param) :
       
    61         """
       
    62             Add the given Parameter to the end of this section's params
       
    63         """
       
    64 
       
    65         self.params.append(param)
       
    66 
       
    67     def add_params (self, params) :
       
    68         for param in params :
       
    69             self.add_param(param)
       
    70 
       
    71     def add_decl (self, decl) :
       
    72         """
       
    73             Add the given Declaration to the end of this section's decls
       
    74         """
       
    75 
       
    76         self.decls.append(decl)
       
    77 
       
    78     def add_decls (self, decls) :
       
    79         for decl in decls :
       
    80             self.add_decl(decl)
       
    81 
       
    82     def fmt_lines (self) :
       
    83         """
       
    84             Format all of our params and decls, in that order
       
    85         """
       
    86 
       
    87         # then output each content line
       
    88         for stmt in itertools.chain(self.params, self.decls) :
       
    89             # skip Nones
       
    90             if stmt is None :
       
    91                 continue
       
    92 
       
    93             for line in stmt.fmt_lines() :
       
    94                 yield line
       
    95 
       
    96 class Section (_Section) :
       
    97     """
       
    98         A section holds a list of params and a list of decls, plus some comments at the beginning of the section
       
    99     """
       
   100     
       
   101     def fmt_lines (self) :
       
   102         """
       
   103             Format all of our comments, and then super
       
   104         """
       
   105 
       
   106         # comments
       
   107         for line in self._fmt_comments() :
       
   108             yield line
       
   109 
       
   110         # section stuff
       
   111         for line in _Section.fmt_lines(self) :
       
   112             yield line
       
   113 
       
   114 class ConfFile (Section, conf.File) :
       
   115     DEFAULT_NAME = "dhcpd.conf"
       
   116     DEFAULT_PATH = "/etc/dhcp3/dhcpd.conf"
       
   117 
       
   118     def __init__ (self, name=DEFAULT_NAME, path=DEFAULT_PATH, params=None, decls=None, comment=None) :
       
   119         """
       
   120             Initialize the dhcpd config file, but don't open it yet.
       
   121         """
       
   122         
       
   123         conf.File.__init__(self, name, path)
       
   124         Section.__init__(self, params, decls, comment)
       
   125 
       
   126 class Statement (Object) :
       
   127     """
       
   128         A statement is a single line in the config file
       
   129     """
       
   130 
       
   131     def __init__ (self, name, *args, **kwargs) :
       
   132         """
       
   133             Arguments given as None will be ignored.
       
   134 
       
   135             A comment can be given as a keyword argument
       
   136         """
       
   137 
       
   138         if kwargs : assert len(kwargs) == 1 and 'comment' in kwargs
       
   139 
       
   140         Object.__init__(self, [kwargs.get('comment')])
       
   141 
       
   142         self.name = name
       
   143         self.args = [arg for arg in args if arg is not None]
       
   144     
       
   145     def _fmt_arg (self, arg) :
       
   146         """
       
   147             Formats a arg for use in output, the following types are supported:
       
   148 
       
   149                 list/tuple/iter:    results in a comma-and-space separated list of formatted values
       
   150                 unicode:            results in an encoded str
       
   151                 str:                results in the string itself, quoted if needed
       
   152                 other:              attempt to convert to a str, and then format that
       
   153         """
       
   154         
       
   155         # format lists specially
       
   156         # XXX: iterators?
       
   157         if isinstance(arg, (list, tuple)) :
       
   158             # recurse as a comma-and-space separated list
       
   159             return ', '.join(self._fmt_arg(a) for a in arg)
       
   160 
       
   161         elif isinstance(arg, Literal) :
       
   162             # use what it specifies
       
   163             return arg.fmt_arg()
       
   164 
       
   165         elif isinstance(arg, unicode) :
       
   166             # recurse with the str version
       
   167             # XXX: what encoding to use?
       
   168             return self._fmt_arg(arg.encode('utf8'))
       
   169 
       
   170         elif isinstance(arg, str) :
       
   171             # XXX: quoting
       
   172             return arg
       
   173         
       
   174         else :
       
   175             # try and use it as a string
       
   176             return self._fmt_arg(str(arg))
       
   177     
       
   178     def _fmt_data (self) :
       
   179         """
       
   180             Formats the statement name/params as a single line, ignoring None
       
   181         """
       
   182 
       
   183         return "%s%s" % (self.name, (' ' + ' '.join(self._fmt_arg(a) for a in self.args)) if self.args else '')
       
   184 
       
   185 class Literal (Statement) :
       
   186     """
       
   187         A literal is something that goes into the config file as-is, with no formatting or escaping applied.
       
   188     """
       
   189 
       
   190     def __init__ (self, literal) :
       
   191         self.literal = literal
       
   192     
       
   193     def fmt_arg (self) :
       
   194         return self.literal
       
   195 
       
   196     def fmt_lines (self) :
       
   197         yield self.literal
       
   198 
       
   199 class Parameter (Statement) :
       
   200     """
       
   201         A parameter is a single statement that configures the behaviour of something.
       
   202 
       
   203         Parameters have a name, and optionally, a number of arguments, and are formatted as statements terminated with
       
   204         a semicolon. For convenience, params/decls that are None are ignored.
       
   205             
       
   206         The parameter will be formatted like this:
       
   207             <name> [ <arg> [ ... ] ] ";"
       
   208     """
       
   209     
       
   210     def fmt_lines (self) :
       
   211         """
       
   212             Yields a single ;-terminated line
       
   213         """
       
   214         
       
   215         # comments
       
   216         for line in self._fmt_comments() :
       
   217             yield line
       
   218         
       
   219         # the line
       
   220         yield "%s;" % self._fmt_data()
       
   221 
       
   222 class Declaration (_Section, Statement) :
       
   223     """
       
   224         A declaration begins like a statement (with name and args), but then contains a curly-braces-delimited block
       
   225         that acts like a Section.
       
   226 
       
   227         <name> [ <args> [ ... ] ] {
       
   228             [ <Section> ]
       
   229         }
       
   230         
       
   231     """
       
   232 
       
   233     def __init__ (self, name, args=[], params=None, decls=None, comment=None) :
       
   234         """
       
   235             The name/args will be formatted as in Statement, but params should be an iterable of Parameters, and decls
       
   236             an iterable of Declarations.
       
   237         """
       
   238         
       
   239         # init the statement bit
       
   240         _Section.__init__(self, params, decls)
       
   241         Statement.__init__(self, name, *args, **dict(comment=comment))
       
   242 
       
   243     def fmt_lines (self) :
       
   244         """
       
   245             Yields a header line, a series of indented body lines, and the footer line
       
   246         """
       
   247         
       
   248         # comments
       
   249         for line in self._fmt_comments() :
       
   250             yield line
       
   251         
       
   252         # the header to open the block
       
   253         yield "%s {" % self._fmt_data()
       
   254         
       
   255         # then output the section stuff, indented
       
   256         for line in _Section.fmt_lines(self) :
       
   257             yield "\t%s" % line
       
   258 
       
   259         # and then close the block
       
   260         yield "}"
       
   261 
       
   262 class SharedNetwork (Declaration) :
       
   263     """
       
   264         A shared-network declaration is used to define a set of subnets that share the same physical network,
       
   265         optionally with some shared params.
       
   266 
       
   267         shared-network <name> {
       
   268             [ parameters ]
       
   269             [ declarations ]
       
   270         }
       
   271     """
       
   272 
       
   273     def __init__ (self, name, *args, **kwargs) :
       
   274         """
       
   275             @param name the name of the shared-subnet
       
   276         """
       
   277 
       
   278         super(SharedNetwork, self).__init__("shared-network", [name], *args, **kwargs)
       
   279 
       
   280 class Subnet (Declaration) :
       
   281     """
       
   282         A subnet is used to provide the information about a subnet required to identify whether or not an IP address is
       
   283         on that subnet, and may also be used to specify parameters/declarations for that subnet.
       
   284         
       
   285         subnet <subnet-number> netmask <netmask> {
       
   286             [ parameters ]
       
   287             [ declarations ]
       
   288         }
       
   289     """
       
   290 
       
   291     def __init__ (self, network, *args, **kwargs) :
       
   292         """
       
   293             @param network the addr.Network for the subnet
       
   294         """
       
   295 
       
   296         super(Subnet, self).__init__("subnet", [network.net(), "netmask", network.netmask()], *args, **kwargs)
       
   297 
       
   298 class Group (Declaration) :
       
   299     """
       
   300         A group is simply used to apply a set of parameters to a set of declarations.
       
   301 
       
   302         group {
       
   303             [ parameters ]
       
   304             [ declarations ]
       
   305         }
       
   306     """
       
   307 
       
   308     def __init__ (self, *args, **kwargs) :
       
   309         super(Group, self).__init__("group", [], *args, **kwargs)
       
   310 
       
   311 
       
   312 class Host (Declaration) :
       
   313     """
       
   314         A host is used to match a request against specific host, and then apply settings for that host.
       
   315 
       
   316         The "hostname" is the DHCP name to identify the host. 
       
   317 
       
   318         If no dhcp-client-identifier option is specified in the parameters, then the host is matched using the
       
   319         "hardware" parameter.
       
   320 
       
   321         host <hostname> {
       
   322             [ parameters ]
       
   323             [ declarations ]
       
   324         }
       
   325     """
       
   326 
       
   327     def __init__ (self, hostname, *args, **kwargs) :
       
   328         super(Host, self).__init__("host", [hostname], *args, **kwargs)
       
   329 
       
   330 class Option (Parameter) :
       
   331     """
       
   332         A generic 'option' parameter for a dhcpd.conf file
       
   333     """
       
   334 
       
   335     def __init__ (self, name, *args, **kwargs) :
       
   336         """
       
   337             Formatted as a Satement with a name of "option <name>".
       
   338         """
       
   339 
       
   340         super(Option, self).__init__("option %s" % name, *args, **kwargs)
       
   341