4 # Copyright (C) 2006, 2007 Google Inc.
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.
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.
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
22 """Tool to do manual changes to the config file.
26 # functions in this module need to have a given name structure, so:
27 # pylint: disable-msg=C0103
35 _wd = readline.get_completer_delims()
36 _wd = _wd.replace("-", "")
37 readline.set_completer_delims(_wd)
42 from ganeti import errors
43 from ganeti import config
44 from ganeti import objects
47 class ConfigShell(cmd.Cmd):
48 """Command tool for editing the config file.
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.
58 def __init__(self, cfg_file=None):
59 """Constructor for the ConfigShell object.
61 The optional cfg_file argument will be used to load a config file
65 cmd.Cmd.__init__(self)
70 self.do_load(cfg_file)
71 self.postcmd(False, "")
74 """Empty line handling.
76 Note that the default will re-run the last command. We don't want
77 that, and just ignore the empty line.
83 def _get_entries(obj):
84 """Computes the list of subdirs and files in the given object.
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.
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)):
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))
105 entries.append(str(idx))
106 elif isinstance(obj, dict):
111 def precmd(self, line):
112 """Precmd hook to prevent commands in invalid states.
114 This will prevent everything except load and quit when no
115 configuration is loaded.
118 if line.startswith("load") or line == 'EOF' or line == "quit":
120 if not self.parents or self.cfg is None:
121 print "No config data loaded"
125 def postcmd(self, stop, line):
126 """Postcmd hook to update the prompt.
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
134 self.prompt = "(#no config) "
136 self.prompt = "(/%s) " % ("/".join(self.path),)
139 def do_load(self, line):
142 Syntax: load [/path/to/config/file]
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.
154 self.cfg = config.ConfigWriter(cfg_file=arg, offline=True)
155 self.cfg._OpenConfig()
156 self.parents = [self.cfg._config_data]
158 except errors.ConfigurationError, err:
159 print "Error: %s" % str(err)
162 def do_ls(self, line):
163 """List the current entry.
165 This will show directories with a slash appended and files
169 dirs, entries = self._get_entries(self.parents[-1])
176 def complete_cd(self, text, line, begidx, endidx):
177 """Completion function for the cd command.
180 pointer = self.parents[-1]
181 dirs, entries = self._get_entries(pointer)
182 matches = [str(name) for name in dirs if name.startswith(text)]
185 def do_cd(self, line):
186 """Changes the current path.
188 Valid arguments: either .., /, "" (no argument) or a child of the current
198 print "Already at top level"
200 elif len(line) == 0 or line == "/":
201 self.parents = self.parents[0:1]
205 pointer = self.parents[-1]
206 dirs, entries = self._get_entries(pointer)
209 print "No such child"
211 if isinstance(pointer, (dict, list, tuple)):
212 if isinstance(pointer, (list, tuple)):
214 new_obj = pointer[line]
216 new_obj = getattr(pointer, line)
217 self.parents.append(new_obj)
218 self.path.append(str(line))
221 def do_pwd(self, line):
222 """Shows the current path.
224 This duplicates the prompt functionality, but it's reasonable to
228 print "/" + "/".join(self.path)
231 def complete_cat(self, text, line, begidx, endidx):
232 """Completion for the cat command.
235 pointer = self.parents[-1]
236 dirs, entries = self._get_entries(pointer)
237 matches = [name for name in entries if name.startswith(text)]
240 def do_cat(self, line):
241 """Shows the contents of the given file.
243 This will display the contents of the given file, which must be a
244 child of the current path (as shows by `ls`).
247 pointer = self.parents[-1]
248 dirs, entries = self._get_entries(pointer)
249 if line not in entries:
250 print "No such entry"
253 if isinstance(pointer, (dict, list, tuple)):
254 if isinstance(pointer, (list, tuple)):
258 val = getattr(pointer, line)
262 def do_verify(self, line):
263 """Verify the configuration.
265 This verifies the contents of the configuration file (and not the
266 in-memory data, as every modify operation automatically saves the
270 vdata = self.cfg.VerifyConfig()
272 print "Validation failed. Errors:"
277 def do_save(self, line):
278 """Saves the configuration data.
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
285 if self.cfg.VerifyConfig():
286 print "Config data does not validate, refusing to save."
288 self.cfg._WriteConfig()
290 def do_rm(self, line):
291 """Removes an instance or a node.
293 This function works only on instances or nodes. You must be in
294 either `/nodes` or `/instances` and give a valid argument.
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"
302 if pointer == data.instances:
303 if line in data.instances:
304 self.cfg.RemoveInstance(line)
306 print "Invalid instance name"
308 if line in data.nodes:
309 self.cfg.RemoveNode(line)
311 print "Invalid node name"
313 def do_EOF(self, line):
314 """Exit the application.
320 def do_quit(self, line):
321 """Exit the application.
328 class Error(Exception):
329 """Generic exception"""
334 """Parses the command line options.
336 In case of command line errors, it will show the usage and exit the
340 (options, args), as returned by OptionParser.parse_args
343 parser = optparse.OptionParser()
345 options, args = parser.parse_args()
351 """Application entry point.
353 This is just a wrapper over BootStrap, to handle our own exceptions.
355 options, args = ParseOptions()
360 shell = ConfigShell(cfg_file=cfg_file)
364 if __name__ == "__main__":