|
1 """ |
|
2 Configuration file output for the ISC DHCP server |
|
3 """ |
|
4 |
|
5 import conf |
|
6 |
|
7 import itertools |
|
8 |
|
9 class ConfDHCP (conf.File) : |
|
10 def __init__ (self, name="dhcpd.conf", path="/etc/dhcp3/dhcpd.conf") : |
|
11 """ |
|
12 Initialize the dhcpd config file, but don't open it yet |
|
13 |
|
14 @see conf.ConfFile.__init__ |
|
15 """ |
|
16 |
|
17 super(ConfDHCP, self).__init__(name, path) |
|
18 |
|
19 class Statement (conf.Object) : |
|
20 """ |
|
21 A statement is a single line in the config file |
|
22 """ |
|
23 |
|
24 def __init__ (self, name, *args) : |
|
25 """ |
|
26 The statement will be formatted like this: |
|
27 <name> [ <arg> [ ... ] ] ";" |
|
28 """ |
|
29 |
|
30 self.name = name |
|
31 self.args = args |
|
32 |
|
33 def _fmt_arg (self, arg) : |
|
34 """ |
|
35 Formats a arg for use in output, the following types are supported: |
|
36 |
|
37 list/tuple/iter: results in a comma-and-space separated list of formatted values |
|
38 unicode: results in an encoded str |
|
39 str: results in the string itself, quoted if needed |
|
40 other: attempt to convert to a str, and then format that |
|
41 """ |
|
42 |
|
43 # format lists specially |
|
44 # XXX: iterators? |
|
45 if isinstance(arg, (list, tuple)) : |
|
46 # recurse as a comma-and-space separated list |
|
47 return ', '.join(self._fmt_arg(a) for a in arg) |
|
48 |
|
49 elif isinstance(arg, Literal) : |
|
50 # use what it specifies |
|
51 return arg.fmt_arg() |
|
52 |
|
53 elif isinstance(arg, unicode) : |
|
54 # recurse with the str version |
|
55 # XXX: what encoding to use? |
|
56 return self._fmt_arg(arg.encode('utf8')) |
|
57 |
|
58 elif isinstance(arg, str) : |
|
59 # XXX: quoting |
|
60 return arg |
|
61 |
|
62 else : |
|
63 # try and use it as a string |
|
64 return self._fmt_arg(str(arg)) |
|
65 |
|
66 def _fmt_data (self) : |
|
67 """ |
|
68 Formats the statement name/params as a single line |
|
69 """ |
|
70 |
|
71 return "%s%s" % (self.name, (' ' + ' '.join(self._fmt_arg(a) for a in self.args)) if self.args else '') |
|
72 |
|
73 class Literal (Statement) : |
|
74 """ |
|
75 A literal is something that goes into the config file as-is, with no formatting or escaping applied. |
|
76 """ |
|
77 |
|
78 def __init__ (self, literal) : |
|
79 self.literal = literal |
|
80 |
|
81 def fmt_arg (self) : |
|
82 return self.literal |
|
83 |
|
84 def fmt_lines (self) : |
|
85 yield self.literal |
|
86 |
|
87 class Parameter (Statement) : |
|
88 """ |
|
89 A parameter is a single statement that configures the behaviour of something. |
|
90 |
|
91 Parameters have a name, and optionally, a number of arguments, and are formatted as statements terminated with |
|
92 a semicolon. |
|
93 """ |
|
94 |
|
95 def fmt_lines (self) : |
|
96 """ |
|
97 Yields a single ;-terminated line |
|
98 """ |
|
99 |
|
100 yield "%s;" % self._fmt_data() |
|
101 |
|
102 class Declaration (Statement) : |
|
103 """ |
|
104 A declaration begins like a statement (with name and args), but then contains a block of any number of |
|
105 parameters followed by any number of nested declarations. |
|
106 |
|
107 <name> [ <args> [ ... ] ] { |
|
108 [ <parameters> ] |
|
109 [ <declarations> ] |
|
110 } |
|
111 |
|
112 """ |
|
113 |
|
114 def __init__ (self, name, args=[], params=[], decls=[]) : |
|
115 """ |
|
116 The name/args will be formatted as in Statement, but params should be an iterable of Parameters, and decls |
|
117 an iterable of Declarations. |
|
118 """ |
|
119 |
|
120 # init the statement bit |
|
121 Statement.__init__(self, name, *args) |
|
122 |
|
123 # store the iterables |
|
124 self.params = params |
|
125 self.decls = decls |
|
126 |
|
127 def fmt_lines (self) : |
|
128 """ |
|
129 Yields a header line, a series of indented body lines, and the footer line |
|
130 """ |
|
131 |
|
132 # the header to open the block |
|
133 yield "%s {" % self._fmt_data() |
|
134 |
|
135 # then output each content line |
|
136 for stmt in itertools.chain(self.params, self.decls) : |
|
137 # ..indented |
|
138 for line in stmt.fmt_lines() : |
|
139 yield "\t%s" % line |
|
140 |
|
141 # and then close the block |
|
142 yield "}" |
|
143 |
|
144 class SharedNetwork (Declaration) : |
|
145 """ |
|
146 A shared-network declaration is used to define a set of subnets that share the same physical network, |
|
147 optionally with some shared params. |
|
148 |
|
149 shared-network <name> { |
|
150 [ parameters ] |
|
151 [ declarations ] |
|
152 } |
|
153 """ |
|
154 |
|
155 def __init__ (self, name, params=[], decls=[]) : |
|
156 """ |
|
157 @param name the name of the shared-subnet |
|
158 @param params optional parameters |
|
159 @param decls the iterable of subnets or other declarations in the shared network |
|
160 """ |
|
161 |
|
162 super(SharedNetwork, self).__init__("shared-network", [name], params, decls) |
|
163 |
|
164 class Subnet (Declaration) : |
|
165 """ |
|
166 A subnet is used to provide the information about a subnet required to identify whether or not an IP address is |
|
167 on that subnet, and may also be used to specify parameters/declarations for that subnet. |
|
168 |
|
169 subnet <subnet-number> netmask <netmask> { |
|
170 [ parameters ] |
|
171 [ declarations ] |
|
172 } |
|
173 """ |
|
174 |
|
175 def __init__ (self, network, params=[], decls=[]) : |
|
176 """ |
|
177 @param network the addr.Network for the subnet |
|
178 @param params optional parameters |
|
179 @param decls optional decls, e.g. subnets |
|
180 """ |
|
181 |
|
182 super(Subnet, self).__init__("subnet", [network.net(), "netmask", network.netmask()], params, decls) |
|
183 |
|
184 class Group (Declaration) : |
|
185 """ |
|
186 A group is simply used to apply a set of parameters to a set of declarations. |
|
187 |
|
188 group { |
|
189 [ parameters ] |
|
190 [ declarations ] |
|
191 } |
|
192 """ |
|
193 |
|
194 def __init__ (self, params=[], decls=[]) : |
|
195 super(Group, self).__init__("group", [], params, decls) |
|
196 |
|
197 |
|
198 class Host (Declaration) : |
|
199 """ |
|
200 A host is used to match a request against specific host, and then apply settings for that host. |
|
201 |
|
202 The "hostname" is the DHCP name to identify the host. |
|
203 |
|
204 If no dhcp-client-identifier option is specified in the parameters, then the host is matched using the |
|
205 "hardware" parameter. |
|
206 |
|
207 host <hostname> { |
|
208 [ parameters ] |
|
209 [ declarations ] |
|
210 } |
|
211 """ |
|
212 |
|
213 def __init__ (self, hostname, params=[], decls=[]) : |
|
214 super(Host, self).__init__("host", [hostname], params, decls) |
|
215 |
|
216 class Option (Parameter) : |
|
217 """ |
|
218 A generic 'option' parameter for a dhcpd.conf file |
|
219 """ |
|
220 |
|
221 def __init__ (self, name, *args) : |
|
222 """ |
|
223 Formatted as a Satement with a name of "option <name>". |
|
224 """ |
|
225 |
|
226 super(Option, self).__init__("option %s" % name, *args) |
|
227 |