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