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 |
|