Remove useless code in backend for network hooks
[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=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   # all do_/complete_* functions follow the same API
57   # pylint: disable=W0613
58   prompt = "(/) "
59
60   def __init__(self, cfg_file=None):
61     """Constructor for the ConfigShell object.
62
63     The optional cfg_file argument will be used to load a config file
64     at startup.
65
66     """
67     cmd.Cmd.__init__(self)
68     self.cfg = None
69     self.parents = []
70     self.path = []
71     if cfg_file:
72       self.do_load(cfg_file)
73       self.postcmd(False, "")
74
75   def emptyline(self):
76     """Empty line handling.
77
78     Note that the default will re-run the last command. We don't want
79     that, and just ignore the empty line.
80
81     """
82     return False
83
84   @staticmethod
85   def _get_entries(obj):
86     """Computes the list of subdirs and files in the given object.
87
88     This, depending on the passed object entry, look at each logical
89     child of the object and decides if it's a container or a simple
90     object. Based on this, it computes the list of subdir and files.
91
92     """
93     dirs = []
94     entries = []
95     if isinstance(obj, objects.ConfigObject):
96       for name in obj.GetAllSlots():
97         child = getattr(obj, name, None)
98         if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
99           dirs.append(name)
100         else:
101           entries.append(name)
102     elif isinstance(obj, (list, tuple)):
103       for idx, child in enumerate(obj):
104         if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
105           dirs.append(str(idx))
106         else:
107           entries.append(str(idx))
108     elif isinstance(obj, dict):
109       dirs = obj.keys()
110
111     return dirs, entries
112
113   def precmd(self, line):
114     """Precmd hook to prevent commands in invalid states.
115
116     This will prevent everything except load and quit when no
117     configuration is loaded.
118
119     """
120     if line.startswith("load") or line == "EOF" or line == "quit":
121       return line
122     if not self.parents or self.cfg is None:
123       print "No config data loaded"
124       return ""
125     return line
126
127   def postcmd(self, stop, line):
128     """Postcmd hook to update the prompt.
129
130     We show the current location in the prompt and this function is
131     used to update it; this is only needed after cd and load, but we
132     update it anyway.
133
134     """
135     if self.cfg is None:
136       self.prompt = "(#no config) "
137     else:
138       self.prompt = "(/%s) " % ("/".join(self.path),)
139     return stop
140
141   def do_load(self, line):
142     """Load function.
143
144     Syntax: load [/path/to/config/file]
145
146     This will load a new configuration, discarding any existing data
147     (if any). If no argument has been passed, it will use the default
148     config file location.
149
150     """
151     if line:
152       arg = line
153     else:
154       arg = None
155     try:
156       self.cfg = config.ConfigWriter(cfg_file=arg, offline=True)
157       self.parents = [self.cfg._config_data] # pylint: disable=W0212
158       self.path = []
159     except errors.ConfigurationError, err:
160       print "Error: %s" % str(err)
161     return False
162
163   def do_ls(self, line):
164     """List the current entry.
165
166     This will show directories with a slash appended and files
167     normally.
168
169     """
170     dirs, entries = self._get_entries(self.parents[-1])
171     for i in dirs:
172       print i + "/"
173     for i in entries:
174       print i
175     return False
176
177   def complete_cd(self, text, line, begidx, endidx):
178     """Completion function for the cd command.
179
180     """
181     pointer = self.parents[-1]
182     dirs, _ = self._get_entries(pointer)
183     matches = [str(name) for name in dirs if name.startswith(text)]
184     return matches
185
186   def do_cd(self, line):
187     """Changes the current path.
188
189     Valid arguments: either .., /, "" (no argument) or a child of the current
190     object.
191
192     """
193     if line == "..":
194       if self.path:
195         self.path.pop()
196         self.parents.pop()
197         return False
198       else:
199         print "Already at top level"
200         return False
201     elif len(line) == 0 or line == "/":
202       self.parents = self.parents[0:1]
203       self.path = []
204       return False
205
206     pointer = self.parents[-1]
207     dirs, _ = self._get_entries(pointer)
208
209     if line not in dirs:
210       print "No such child"
211       return False
212     if isinstance(pointer, (dict, list, tuple)):
213       if isinstance(pointer, (list, tuple)):
214         line = int(line)
215       new_obj = pointer[line]
216     else:
217       new_obj = getattr(pointer, line)
218     self.parents.append(new_obj)
219     self.path.append(str(line))
220     return False
221
222   def do_pwd(self, line):
223     """Shows the current path.
224
225     This duplicates the prompt functionality, but it's reasonable to
226     have.
227
228     """
229     print "/" + "/".join(self.path)
230     return False
231
232   def complete_cat(self, text, line, begidx, endidx):
233     """Completion for the cat command.
234
235     """
236     pointer = self.parents[-1]
237     _, entries = self._get_entries(pointer)
238     matches = [name for name in entries if name.startswith(text)]
239     return matches
240
241   def do_cat(self, line):
242     """Shows the contents of the given file.
243
244     This will display the contents of the given file, which must be a
245     child of the current path (as shows by `ls`).
246
247     """
248     pointer = self.parents[-1]
249     _, entries = self._get_entries(pointer)
250     if line not in entries:
251       print "No such entry"
252       return False
253
254     if isinstance(pointer, (dict, list, tuple)):
255       if isinstance(pointer, (list, tuple)):
256         line = int(line)
257       val = pointer[line]
258     else:
259       val = getattr(pointer, line)
260     print val
261     return False
262
263   def do_verify(self, line):
264     """Verify the configuration.
265
266     This verifies the contents of the configuration file (and not the
267     in-memory data, as every modify operation automatically saves the
268     file).
269
270     """
271     vdata = self.cfg.VerifyConfig()
272     if vdata:
273       print "Validation failed. Errors:"
274       for text in vdata:
275         print text
276     return False
277
278   def do_save(self, line):
279     """Saves the configuration data.
280
281     Note that is redundant (all modify operations automatically save
282     the data), but it is good to use it as in the future that could
283     change.
284
285     """
286     if self.cfg.VerifyConfig():
287       print "Config data does not validate, refusing to save."
288       return False
289     self.cfg._WriteConfig() # pylint: disable=W0212
290
291   def do_rm(self, line):
292     """Removes an instance or a node.
293
294     This function works only on instances or nodes. You must be in
295     either `/nodes` or `/instances` and give a valid argument.
296
297     """
298     pointer = self.parents[-1]
299     data = self.cfg._config_data  # pylint: disable=W0212
300     if pointer not in (data.instances, data.nodes):
301       print "Can only delete instances and nodes"
302       return False
303     if pointer == data.instances:
304       if line in data.instances:
305         self.cfg.RemoveInstance(line)
306       else:
307         print "Invalid instance name"
308     else:
309       if line in data.nodes:
310         self.cfg.RemoveNode(line)
311       else:
312         print "Invalid node name"
313
314   @staticmethod
315   def do_EOF(line):
316     """Exit the application.
317
318     """
319     print
320     return True
321
322   @staticmethod
323   def do_quit(line):
324     """Exit the application.
325
326     """
327     print
328     return True
329
330
331 class Error(Exception):
332   """Generic exception"""
333   pass
334
335
336 def ParseOptions():
337   """Parses the command line options.
338
339   In case of command line errors, it will show the usage and exit the
340   program.
341
342   @return: a tuple (options, args), as returned by OptionParser.parse_args
343
344   """
345   parser = optparse.OptionParser()
346
347   options, args = parser.parse_args()
348
349   return options, args
350
351
352 def main():
353   """Application entry point.
354
355   """
356   _, args = ParseOptions()
357   if args:
358     cfg_file = args[0]
359   else:
360     cfg_file = None
361   shell = ConfigShell(cfg_file=cfg_file)
362   shell.cmdloop()
363
364
365 if __name__ == "__main__":
366   main()