root / tools / cfgshell @ 2ec08468
History | View | Annotate | Download (8.9 kB)
1 | a8083063 | Iustin Pop | #!/usr/bin/python |
---|---|---|---|
2 | a8083063 | Iustin Pop | # |
3 | a8083063 | Iustin Pop | |
4 | a8083063 | Iustin Pop | # Copyright (C) 2006, 2007 Google Inc. |
5 | a8083063 | Iustin Pop | # |
6 | a8083063 | Iustin Pop | # This program is free software; you can redistribute it and/or modify |
7 | a8083063 | Iustin Pop | # it under the terms of the GNU General Public License as published by |
8 | a8083063 | Iustin Pop | # the Free Software Foundation; either version 2 of the License, or |
9 | a8083063 | Iustin Pop | # (at your option) any later version. |
10 | a8083063 | Iustin Pop | # |
11 | a8083063 | Iustin Pop | # This program is distributed in the hope that it will be useful, but |
12 | a8083063 | Iustin Pop | # WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | a8083063 | Iustin Pop | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | a8083063 | Iustin Pop | # General Public License for more details. |
15 | a8083063 | Iustin Pop | # |
16 | a8083063 | Iustin Pop | # You should have received a copy of the GNU General Public License |
17 | a8083063 | Iustin Pop | # along with this program; if not, write to the Free Software |
18 | a8083063 | Iustin Pop | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
19 | a8083063 | Iustin Pop | # 02110-1301, USA. |
20 | a8083063 | Iustin Pop | |
21 | a8083063 | Iustin Pop | |
22 | a8083063 | Iustin Pop | """Tool to do manual changes to the config file. |
23 | a8083063 | Iustin Pop | |
24 | a8083063 | Iustin Pop | """ |
25 | a8083063 | Iustin Pop | |
26 | 3ecf6786 | Iustin Pop | # functions in this module need to have a given name structure, so: |
27 | 3ecf6786 | Iustin Pop | # pylint: disable-msg=C0103 |
28 | 3ecf6786 | Iustin Pop | |
29 | a8083063 | Iustin Pop | |
30 | a8083063 | Iustin Pop | import optparse |
31 | a8083063 | Iustin Pop | import cmd |
32 | a8083063 | Iustin Pop | |
33 | a8083063 | Iustin Pop | try: |
34 | a8083063 | Iustin Pop | import readline |
35 | a8083063 | Iustin Pop | _wd = readline.get_completer_delims() |
36 | a8083063 | Iustin Pop | _wd = _wd.replace("-", "") |
37 | a8083063 | Iustin Pop | readline.set_completer_delims(_wd) |
38 | a8083063 | Iustin Pop | del _wd |
39 | a8083063 | Iustin Pop | except ImportError: |
40 | a8083063 | Iustin Pop | pass |
41 | a8083063 | Iustin Pop | |
42 | a8083063 | Iustin Pop | from ganeti import errors |
43 | a8083063 | Iustin Pop | from ganeti import config |
44 | a8083063 | Iustin Pop | from ganeti import objects |
45 | a8083063 | Iustin Pop | |
46 | a8083063 | Iustin Pop | |
47 | a8083063 | Iustin Pop | class ConfigShell(cmd.Cmd): |
48 | a8083063 | Iustin Pop | """Command tool for editing the config file. |
49 | a8083063 | Iustin Pop | |
50 | a8083063 | Iustin Pop | Note that although we don't do saves after remove, the current |
51 | a8083063 | Iustin Pop | ConfigWriter code does that; so we can't prevent someone from |
52 | a8083063 | Iustin Pop | actually breaking the config with this tool. It's the users' |
53 | a8083063 | Iustin Pop | responsibility to know what they're doing. |
54 | a8083063 | Iustin Pop | |
55 | a8083063 | Iustin Pop | """ |
56 | a8083063 | Iustin Pop | prompt = "(/) " |
57 | a8083063 | Iustin Pop | |
58 | a8083063 | Iustin Pop | def __init__(self, cfg_file=None): |
59 | a8083063 | Iustin Pop | """Constructor for the ConfigShell object. |
60 | a8083063 | Iustin Pop | |
61 | a8083063 | Iustin Pop | The optional cfg_file argument will be used to load a config file |
62 | a8083063 | Iustin Pop | at startup. |
63 | a8083063 | Iustin Pop | |
64 | a8083063 | Iustin Pop | """ |
65 | a8083063 | Iustin Pop | cmd.Cmd.__init__(self) |
66 | 5fcdc80d | Iustin Pop | self.cfg = None |
67 | a8083063 | Iustin Pop | self.parents = [] |
68 | a8083063 | Iustin Pop | self.path = [] |
69 | a8083063 | Iustin Pop | if cfg_file: |
70 | a8083063 | Iustin Pop | self.do_load(cfg_file) |
71 | a8083063 | Iustin Pop | self.postcmd(False, "") |
72 | a8083063 | Iustin Pop | |
73 | a8083063 | Iustin Pop | def emptyline(self): |
74 | a8083063 | Iustin Pop | """Empty line handling. |
75 | a8083063 | Iustin Pop | |
76 | a8083063 | Iustin Pop | Note that the default will re-run the last command. We don't want |
77 | a8083063 | Iustin Pop | that, and just ignore the empty line. |
78 | a8083063 | Iustin Pop | |
79 | a8083063 | Iustin Pop | """ |
80 | a8083063 | Iustin Pop | return False |
81 | a8083063 | Iustin Pop | |
82 | a8083063 | Iustin Pop | @staticmethod |
83 | a8083063 | Iustin Pop | def _get_entries(obj): |
84 | a8083063 | Iustin Pop | """Computes the list of subdirs and files in the given object. |
85 | a8083063 | Iustin Pop | |
86 | a8083063 | Iustin Pop | This, depending on the passed object entry, look at each logical |
87 | a8083063 | Iustin Pop | child of the object and decides if it's a container or a simple |
88 | a8083063 | Iustin Pop | object. Based on this, it computes the list of subdir and files. |
89 | a8083063 | Iustin Pop | |
90 | a8083063 | Iustin Pop | """ |
91 | a8083063 | Iustin Pop | dirs = [] |
92 | a8083063 | Iustin Pop | entries = [] |
93 | a8083063 | Iustin Pop | if isinstance(obj, objects.ConfigObject): |
94 | a8083063 | Iustin Pop | for name in obj.__slots__: |
95 | a8083063 | Iustin Pop | child = getattr(obj, name, None) |
96 | a8083063 | Iustin Pop | if isinstance(child, (list, dict, tuple, objects.ConfigObject)): |
97 | a8083063 | Iustin Pop | dirs.append(name) |
98 | a8083063 | Iustin Pop | else: |
99 | a8083063 | Iustin Pop | entries.append(name) |
100 | a8083063 | Iustin Pop | elif isinstance(obj, (list, tuple)): |
101 | a8083063 | Iustin Pop | for idx, child in enumerate(obj): |
102 | a8083063 | Iustin Pop | if isinstance(child, (list, dict, tuple, objects.ConfigObject)): |
103 | a8083063 | Iustin Pop | dirs.append(str(idx)) |
104 | a8083063 | Iustin Pop | else: |
105 | a8083063 | Iustin Pop | entries.append(str(idx)) |
106 | a8083063 | Iustin Pop | elif isinstance(obj, dict): |
107 | a8083063 | Iustin Pop | dirs = obj.keys() |
108 | a8083063 | Iustin Pop | |
109 | a8083063 | Iustin Pop | return dirs, entries |
110 | a8083063 | Iustin Pop | |
111 | a8083063 | Iustin Pop | def precmd(self, line): |
112 | a8083063 | Iustin Pop | """Precmd hook to prevent commands in invalid states. |
113 | a8083063 | Iustin Pop | |
114 | a8083063 | Iustin Pop | This will prevent everything except load and quit when no |
115 | a8083063 | Iustin Pop | configuration is loaded. |
116 | a8083063 | Iustin Pop | |
117 | a8083063 | Iustin Pop | """ |
118 | a8083063 | Iustin Pop | if line.startswith("load") or line == 'EOF' or line == "quit": |
119 | a8083063 | Iustin Pop | return line |
120 | a8083063 | Iustin Pop | if not self.parents or self.cfg is None: |
121 | a8083063 | Iustin Pop | print "No config data loaded" |
122 | a8083063 | Iustin Pop | return "" |
123 | a8083063 | Iustin Pop | return line |
124 | a8083063 | Iustin Pop | |
125 | a8083063 | Iustin Pop | def postcmd(self, stop, line): |
126 | a8083063 | Iustin Pop | """Postcmd hook to update the prompt. |
127 | a8083063 | Iustin Pop | |
128 | a8083063 | Iustin Pop | We show the current location in the prompt and this function is |
129 | a8083063 | Iustin Pop | used to update it; this is only needed after cd and load, but we |
130 | a8083063 | Iustin Pop | update it anyway. |
131 | a8083063 | Iustin Pop | |
132 | a8083063 | Iustin Pop | """ |
133 | a8083063 | Iustin Pop | if self.cfg is None: |
134 | a8083063 | Iustin Pop | self.prompt = "(#no config) " |
135 | a8083063 | Iustin Pop | else: |
136 | 5fcdc80d | Iustin Pop | self.prompt = "(/%s) " % ("/".join(self.path),) |
137 | a8083063 | Iustin Pop | return stop |
138 | a8083063 | Iustin Pop | |
139 | a8083063 | Iustin Pop | def do_load(self, line): |
140 | a8083063 | Iustin Pop | """Load function. |
141 | a8083063 | Iustin Pop | |
142 | a8083063 | Iustin Pop | Syntax: load [/path/to/config/file] |
143 | a8083063 | Iustin Pop | |
144 | a8083063 | Iustin Pop | This will load a new configuration, discarding any existing data |
145 | a8083063 | Iustin Pop | (if any). If no argument has been passed, it will use the default |
146 | a8083063 | Iustin Pop | config file location. |
147 | a8083063 | Iustin Pop | |
148 | a8083063 | Iustin Pop | """ |
149 | a8083063 | Iustin Pop | if line: |
150 | a8083063 | Iustin Pop | arg = line |
151 | a8083063 | Iustin Pop | else: |
152 | a8083063 | Iustin Pop | arg = None |
153 | a8083063 | Iustin Pop | try: |
154 | a8083063 | Iustin Pop | self.cfg = config.ConfigWriter(cfg_file=arg, offline=True) |
155 | a8083063 | Iustin Pop | self.cfg._OpenConfig() |
156 | a8083063 | Iustin Pop | self.parents = [self.cfg._config_data] |
157 | a8083063 | Iustin Pop | self.path = [] |
158 | a8083063 | Iustin Pop | except errors.ConfigurationError, err: |
159 | a8083063 | Iustin Pop | print "Error: %s" % str(err) |
160 | a8083063 | Iustin Pop | return False |
161 | a8083063 | Iustin Pop | |
162 | a8083063 | Iustin Pop | def do_ls(self, line): |
163 | a8083063 | Iustin Pop | """List the current entry. |
164 | a8083063 | Iustin Pop | |
165 | a8083063 | Iustin Pop | This will show directories with a slash appended and files |
166 | a8083063 | Iustin Pop | normally. |
167 | a8083063 | Iustin Pop | |
168 | a8083063 | Iustin Pop | """ |
169 | a8083063 | Iustin Pop | dirs, entries = self._get_entries(self.parents[-1]) |
170 | a8083063 | Iustin Pop | for i in dirs: |
171 | a8083063 | Iustin Pop | print i + "/" |
172 | a8083063 | Iustin Pop | for i in entries: |
173 | a8083063 | Iustin Pop | print i |
174 | a8083063 | Iustin Pop | return False |
175 | a8083063 | Iustin Pop | |
176 | a8083063 | Iustin Pop | def complete_cd(self, text, line, begidx, endidx): |
177 | a8083063 | Iustin Pop | """Completion function for the cd command. |
178 | a8083063 | Iustin Pop | |
179 | a8083063 | Iustin Pop | """ |
180 | a8083063 | Iustin Pop | pointer = self.parents[-1] |
181 | a8083063 | Iustin Pop | dirs, entries = self._get_entries(pointer) |
182 | a8083063 | Iustin Pop | matches = [str(name) for name in dirs if name.startswith(text)] |
183 | a8083063 | Iustin Pop | return matches |
184 | a8083063 | Iustin Pop | |
185 | a8083063 | Iustin Pop | def do_cd(self, line): |
186 | a8083063 | Iustin Pop | """Changes the current path. |
187 | a8083063 | Iustin Pop | |
188 | 033b7b64 | Michael Hanselmann | Valid arguments: either .., /, "" (no argument) or a child of the current |
189 | 033b7b64 | Michael Hanselmann | object. |
190 | a8083063 | Iustin Pop | |
191 | a8083063 | Iustin Pop | """ |
192 | a8083063 | Iustin Pop | if line == "..": |
193 | a8083063 | Iustin Pop | if self.path: |
194 | a8083063 | Iustin Pop | self.path.pop() |
195 | a8083063 | Iustin Pop | self.parents.pop() |
196 | a8083063 | Iustin Pop | return False |
197 | a8083063 | Iustin Pop | else: |
198 | a8083063 | Iustin Pop | print "Already at top level" |
199 | a8083063 | Iustin Pop | return False |
200 | 033b7b64 | Michael Hanselmann | elif len(line) == 0 or line == "/": |
201 | 033b7b64 | Michael Hanselmann | self.parents = self.parents[0:1] |
202 | 033b7b64 | Michael Hanselmann | self.path = [] |
203 | 033b7b64 | Michael Hanselmann | return False |
204 | a8083063 | Iustin Pop | |
205 | a8083063 | Iustin Pop | pointer = self.parents[-1] |
206 | a8083063 | Iustin Pop | dirs, entries = self._get_entries(pointer) |
207 | a8083063 | Iustin Pop | |
208 | a8083063 | Iustin Pop | if line not in dirs: |
209 | a8083063 | Iustin Pop | print "No such child" |
210 | a8083063 | Iustin Pop | return False |
211 | a8083063 | Iustin Pop | if isinstance(pointer, (dict, list, tuple)): |
212 | a8083063 | Iustin Pop | if isinstance(pointer, (list, tuple)): |
213 | a8083063 | Iustin Pop | line = int(line) |
214 | a8083063 | Iustin Pop | new_obj = pointer[line] |
215 | a8083063 | Iustin Pop | else: |
216 | a8083063 | Iustin Pop | new_obj = getattr(pointer, line) |
217 | a8083063 | Iustin Pop | self.parents.append(new_obj) |
218 | a8083063 | Iustin Pop | self.path.append(str(line)) |
219 | a8083063 | Iustin Pop | return False |
220 | a8083063 | Iustin Pop | |
221 | a8083063 | Iustin Pop | def do_pwd(self, line): |
222 | a8083063 | Iustin Pop | """Shows the current path. |
223 | a8083063 | Iustin Pop | |
224 | a8083063 | Iustin Pop | This duplicates the prompt functionality, but it's reasonable to |
225 | a8083063 | Iustin Pop | have. |
226 | a8083063 | Iustin Pop | |
227 | a8083063 | Iustin Pop | """ |
228 | a8083063 | Iustin Pop | print "/" + "/".join(self.path) |
229 | a8083063 | Iustin Pop | return False |
230 | a8083063 | Iustin Pop | |
231 | a8083063 | Iustin Pop | def complete_cat(self, text, line, begidx, endidx): |
232 | a8083063 | Iustin Pop | """Completion for the cat command. |
233 | a8083063 | Iustin Pop | |
234 | a8083063 | Iustin Pop | """ |
235 | a8083063 | Iustin Pop | pointer = self.parents[-1] |
236 | a8083063 | Iustin Pop | dirs, entries = self._get_entries(pointer) |
237 | a8083063 | Iustin Pop | matches = [name for name in entries if name.startswith(text)] |
238 | a8083063 | Iustin Pop | return matches |
239 | a8083063 | Iustin Pop | |
240 | a8083063 | Iustin Pop | def do_cat(self, line): |
241 | a8083063 | Iustin Pop | """Shows the contents of the given file. |
242 | a8083063 | Iustin Pop | |
243 | a8083063 | Iustin Pop | This will display the contents of the given file, which must be a |
244 | a8083063 | Iustin Pop | child of the current path (as shows by `ls`). |
245 | a8083063 | Iustin Pop | |
246 | a8083063 | Iustin Pop | """ |
247 | a8083063 | Iustin Pop | pointer = self.parents[-1] |
248 | a8083063 | Iustin Pop | dirs, entries = self._get_entries(pointer) |
249 | a8083063 | Iustin Pop | if line not in entries: |
250 | a8083063 | Iustin Pop | print "No such entry" |
251 | a8083063 | Iustin Pop | return False |
252 | a8083063 | Iustin Pop | |
253 | a8083063 | Iustin Pop | if isinstance(pointer, (dict, list, tuple)): |
254 | a8083063 | Iustin Pop | if isinstance(pointer, (list, tuple)): |
255 | a8083063 | Iustin Pop | line = int(line) |
256 | a8083063 | Iustin Pop | val = pointer[line] |
257 | a8083063 | Iustin Pop | else: |
258 | a8083063 | Iustin Pop | val = getattr(pointer, line) |
259 | a8083063 | Iustin Pop | print val |
260 | a8083063 | Iustin Pop | return False |
261 | a8083063 | Iustin Pop | |
262 | a8083063 | Iustin Pop | def do_verify(self, line): |
263 | a8083063 | Iustin Pop | """Verify the configuration. |
264 | a8083063 | Iustin Pop | |
265 | a8083063 | Iustin Pop | This verifies the contents of the configuration file (and not the |
266 | a8083063 | Iustin Pop | in-memory data, as every modify operation automatically saves the |
267 | a8083063 | Iustin Pop | file). |
268 | a8083063 | Iustin Pop | |
269 | a8083063 | Iustin Pop | """ |
270 | a8083063 | Iustin Pop | vdata = self.cfg.VerifyConfig() |
271 | a8083063 | Iustin Pop | if vdata: |
272 | a8083063 | Iustin Pop | print "Validation failed. Errors:" |
273 | a8083063 | Iustin Pop | for text in vdata: |
274 | a8083063 | Iustin Pop | print text |
275 | a8083063 | Iustin Pop | return False |
276 | a8083063 | Iustin Pop | |
277 | a8083063 | Iustin Pop | def do_save(self, line): |
278 | a8083063 | Iustin Pop | """Saves the configuration data. |
279 | a8083063 | Iustin Pop | |
280 | a8083063 | Iustin Pop | Note that is redundant (all modify operations automatically save |
281 | a8083063 | Iustin Pop | the data), but it is good to use it as in the future that could |
282 | a8083063 | Iustin Pop | change. |
283 | a8083063 | Iustin Pop | |
284 | a8083063 | Iustin Pop | """ |
285 | a8083063 | Iustin Pop | if self.cfg.VerifyConfig(): |
286 | a8083063 | Iustin Pop | print "Config data does not validate, refusing to save." |
287 | a8083063 | Iustin Pop | return False |
288 | a8083063 | Iustin Pop | self.cfg._WriteConfig() |
289 | a8083063 | Iustin Pop | |
290 | a8083063 | Iustin Pop | def do_rm(self, line): |
291 | a8083063 | Iustin Pop | """Removes an instance or a node. |
292 | a8083063 | Iustin Pop | |
293 | a8083063 | Iustin Pop | This function works only on instances or nodes. You must be in |
294 | a8083063 | Iustin Pop | either `/nodes` or `/instances` and give a valid argument. |
295 | a8083063 | Iustin Pop | |
296 | a8083063 | Iustin Pop | """ |
297 | a8083063 | Iustin Pop | pointer = self.parents[-1] |
298 | a8083063 | Iustin Pop | data = self.cfg._config_data |
299 | a8083063 | Iustin Pop | if pointer not in (data.instances, data.nodes): |
300 | a8083063 | Iustin Pop | print "Can only delete instances and nodes" |
301 | a8083063 | Iustin Pop | return False |
302 | a8083063 | Iustin Pop | if pointer == data.instances: |
303 | a8083063 | Iustin Pop | if line in data.instances: |
304 | a8083063 | Iustin Pop | self.cfg.RemoveInstance(line) |
305 | a8083063 | Iustin Pop | else: |
306 | a8083063 | Iustin Pop | print "Invalid instance name" |
307 | a8083063 | Iustin Pop | else: |
308 | a8083063 | Iustin Pop | if line in data.nodes: |
309 | a8083063 | Iustin Pop | self.cfg.RemoveNode(line) |
310 | a8083063 | Iustin Pop | else: |
311 | a8083063 | Iustin Pop | print "Invalid node name" |
312 | a8083063 | Iustin Pop | |
313 | a8083063 | Iustin Pop | def do_EOF(self, line): |
314 | 3ecf6786 | Iustin Pop | """Exit the application. |
315 | 3ecf6786 | Iustin Pop | |
316 | 3ecf6786 | Iustin Pop | """ |
317 | a8083063 | Iustin Pop | |
318 | a8083063 | Iustin Pop | return True |
319 | a8083063 | Iustin Pop | |
320 | a8083063 | Iustin Pop | def do_quit(self, line): |
321 | a8083063 | Iustin Pop | """Exit the application. |
322 | a8083063 | Iustin Pop | |
323 | a8083063 | Iustin Pop | """ |
324 | a8083063 | Iustin Pop | |
325 | a8083063 | Iustin Pop | return True |
326 | a8083063 | Iustin Pop | |
327 | 033b7b64 | Michael Hanselmann | |
328 | a8083063 | Iustin Pop | class Error(Exception): |
329 | a8083063 | Iustin Pop | """Generic exception""" |
330 | a8083063 | Iustin Pop | pass |
331 | a8083063 | Iustin Pop | |
332 | a8083063 | Iustin Pop | |
333 | a8083063 | Iustin Pop | def ParseOptions(): |
334 | a8083063 | Iustin Pop | """Parses the command line options. |
335 | a8083063 | Iustin Pop | |
336 | a8083063 | Iustin Pop | In case of command line errors, it will show the usage and exit the |
337 | a8083063 | Iustin Pop | program. |
338 | a8083063 | Iustin Pop | |
339 | a8083063 | Iustin Pop | Returns: |
340 | a8083063 | Iustin Pop | (options, args), as returned by OptionParser.parse_args |
341 | a8083063 | Iustin Pop | """ |
342 | a8083063 | Iustin Pop | |
343 | a8083063 | Iustin Pop | parser = optparse.OptionParser() |
344 | a8083063 | Iustin Pop | |
345 | a8083063 | Iustin Pop | options, args = parser.parse_args() |
346 | a8083063 | Iustin Pop | |
347 | a8083063 | Iustin Pop | return options, args |
348 | a8083063 | Iustin Pop | |
349 | a8083063 | Iustin Pop | |
350 | a8083063 | Iustin Pop | def main(): |
351 | a8083063 | Iustin Pop | """Application entry point. |
352 | a8083063 | Iustin Pop | |
353 | a8083063 | Iustin Pop | This is just a wrapper over BootStrap, to handle our own exceptions. |
354 | a8083063 | Iustin Pop | """ |
355 | a8083063 | Iustin Pop | options, args = ParseOptions() |
356 | a8083063 | Iustin Pop | if args: |
357 | a8083063 | Iustin Pop | cfg_file = args[0] |
358 | a8083063 | Iustin Pop | else: |
359 | a8083063 | Iustin Pop | cfg_file = None |
360 | a8083063 | Iustin Pop | shell = ConfigShell(cfg_file=cfg_file) |
361 | a8083063 | Iustin Pop | shell.cmdloop() |
362 | a8083063 | Iustin Pop | |
363 | a8083063 | Iustin Pop | |
364 | a8083063 | Iustin Pop | if __name__ == "__main__": |
365 | a8083063 | Iustin Pop | main() |