Remove the option to create md/drbd7 instances
[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.cfg._OpenConfig()
156       self.parents = [self.cfg._config_data]
157       self.path = []
158     except errors.ConfigurationError, err:
159       print "Error: %s" % str(err)
160     return False
161
162   def do_ls(self, line):
163     """List the current entry.
164
165     This will show directories with a slash appended and files
166     normally.
167
168     """
169     dirs, entries = self._get_entries(self.parents[-1])
170     for i in dirs:
171       print i + "/"
172     for i in entries:
173       print i
174     return False
175
176   def complete_cd(self, text, line, begidx, endidx):
177     """Completion function for the cd command.
178
179     """
180     pointer = self.parents[-1]
181     dirs, entries = self._get_entries(pointer)
182     matches = [str(name) for name in dirs if name.startswith(text)]
183     return matches
184
185   def do_cd(self, line):
186     """Changes the current path.
187
188     Valid arguments: either .., /, "" (no argument) or a child of the current
189     object.
190
191     """
192     if line == "..":
193       if self.path:
194         self.path.pop()
195         self.parents.pop()
196         return False
197       else:
198         print "Already at top level"
199         return False
200     elif len(line) == 0 or line == "/":
201       self.parents = self.parents[0:1]
202       self.path = []
203       return False
204
205     pointer = self.parents[-1]
206     dirs, entries = self._get_entries(pointer)
207
208     if line not in dirs:
209       print "No such child"
210       return False
211     if isinstance(pointer, (dict, list, tuple)):
212       if isinstance(pointer, (list, tuple)):
213         line = int(line)
214       new_obj = pointer[line]
215     else:
216       new_obj = getattr(pointer, line)
217     self.parents.append(new_obj)
218     self.path.append(str(line))
219     return False
220
221   def do_pwd(self, line):
222     """Shows the current path.
223
224     This duplicates the prompt functionality, but it's reasonable to
225     have.
226
227     """
228     print "/" + "/".join(self.path)
229     return False
230
231   def complete_cat(self, text, line, begidx, endidx):
232     """Completion for the cat command.
233
234     """
235     pointer = self.parents[-1]
236     dirs, entries = self._get_entries(pointer)
237     matches = [name for name in entries if name.startswith(text)]
238     return matches
239
240   def do_cat(self, line):
241     """Shows the contents of the given file.
242
243     This will display the contents of the given file, which must be a
244     child of the current path (as shows by `ls`).
245
246     """
247     pointer = self.parents[-1]
248     dirs, entries = self._get_entries(pointer)
249     if line not in entries:
250       print "No such entry"
251       return False
252
253     if isinstance(pointer, (dict, list, tuple)):
254       if isinstance(pointer, (list, tuple)):
255         line = int(line)
256       val = pointer[line]
257     else:
258       val = getattr(pointer, line)
259     print val
260     return False
261
262   def do_verify(self, line):
263     """Verify the configuration.
264
265     This verifies the contents of the configuration file (and not the
266     in-memory data, as every modify operation automatically saves the
267     file).
268
269     """
270     vdata = self.cfg.VerifyConfig()
271     if vdata:
272       print "Validation failed. Errors:"
273       for text in vdata:
274         print text
275     return False
276
277   def do_save(self, line):
278     """Saves the configuration data.
279
280     Note that is redundant (all modify operations automatically save
281     the data), but it is good to use it as in the future that could
282     change.
283
284     """
285     if self.cfg.VerifyConfig():
286       print "Config data does not validate, refusing to save."
287       return False
288     self.cfg._WriteConfig()
289
290   def do_rm(self, line):
291     """Removes an instance or a node.
292
293     This function works only on instances or nodes. You must be in
294     either `/nodes` or `/instances` and give a valid argument.
295
296     """
297     pointer = self.parents[-1]
298     data = self.cfg._config_data
299     if pointer not in (data.instances, data.nodes):
300       print "Can only delete instances and nodes"
301       return False
302     if pointer == data.instances:
303       if line in data.instances:
304         self.cfg.RemoveInstance(line)
305       else:
306         print "Invalid instance name"
307     else:
308       if line in data.nodes:
309         self.cfg.RemoveNode(line)
310       else:
311         print "Invalid node name"
312
313   def do_EOF(self, line):
314     """Exit the application.
315
316     """
317     print
318     return True
319
320   def do_quit(self, line):
321     """Exit the application.
322
323     """
324     print
325     return True
326
327
328 class Error(Exception):
329   """Generic exception"""
330   pass
331
332
333 def ParseOptions():
334   """Parses the command line options.
335
336   In case of command line errors, it will show the usage and exit the
337   program.
338
339   Returns:
340     (options, args), as returned by OptionParser.parse_args
341   """
342
343   parser = optparse.OptionParser()
344
345   options, args = parser.parse_args()
346
347   return options, args
348
349
350 def main():
351   """Application entry point.
352
353   This is just a wrapper over BootStrap, to handle our own exceptions.
354   """
355   options, args = ParseOptions()
356   if args:
357     cfg_file = args[0]
358   else:
359     cfg_file = None
360   shell = ConfigShell(cfg_file=cfg_file)
361   shell.cmdloop()
362
363
364 if __name__ == "__main__":
365   main()