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=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.
56 # all do_/complete_* functions follow the same API
57 # pylint: disable=W0613
60 def __init__(self, cfg_file=None):
61 """Constructor for the ConfigShell object.
63 The optional cfg_file argument will be used to load a config file
67 cmd.Cmd.__init__(self)
72 self.do_load(cfg_file)
73 self.postcmd(False, "")
76 """Empty line handling.
78 Note that the default will re-run the last command. We don't want
79 that, and just ignore the empty line.
85 def _get_entries(obj):
86 """Computes the list of subdirs and files in the given object.
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.
95 if isinstance(obj, objects.ConfigObject):
96 # pylint: disable=W0212
97 # yes, we're using a protected member
98 for name in obj.GetAllSlots():
99 child = getattr(obj, name, None)
100 if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
104 elif isinstance(obj, (list, tuple)):
105 for idx, child in enumerate(obj):
106 if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
107 dirs.append(str(idx))
109 entries.append(str(idx))
110 elif isinstance(obj, dict):
115 def precmd(self, line):
116 """Precmd hook to prevent commands in invalid states.
118 This will prevent everything except load and quit when no
119 configuration is loaded.
122 if line.startswith("load") or line == 'EOF' or line == "quit":
124 if not self.parents or self.cfg is None:
125 print "No config data loaded"
129 def postcmd(self, stop, line):
130 """Postcmd hook to update the prompt.
132 We show the current location in the prompt and this function is
133 used to update it; this is only needed after cd and load, but we
138 self.prompt = "(#no config) "
140 self.prompt = "(/%s) " % ("/".join(self.path),)
143 def do_load(self, line):
146 Syntax: load [/path/to/config/file]
148 This will load a new configuration, discarding any existing data
149 (if any). If no argument has been passed, it will use the default
150 config file location.
158 self.cfg = config.ConfigWriter(cfg_file=arg, offline=True)
159 self.parents = [self.cfg._config_data] # pylint: disable=W0212
161 except errors.ConfigurationError, err:
162 print "Error: %s" % str(err)
165 def do_ls(self, line):
166 """List the current entry.
168 This will show directories with a slash appended and files
172 dirs, entries = self._get_entries(self.parents[-1])
179 def complete_cd(self, text, line, begidx, endidx):
180 """Completion function for the cd command.
183 pointer = self.parents[-1]
184 dirs, _ = self._get_entries(pointer)
185 matches = [str(name) for name in dirs if name.startswith(text)]
188 def do_cd(self, line):
189 """Changes the current path.
191 Valid arguments: either .., /, "" (no argument) or a child of the current
201 print "Already at top level"
203 elif len(line) == 0 or line == "/":
204 self.parents = self.parents[0:1]
208 pointer = self.parents[-1]
209 dirs, _ = self._get_entries(pointer)
212 print "No such child"
214 if isinstance(pointer, (dict, list, tuple)):
215 if isinstance(pointer, (list, tuple)):
217 new_obj = pointer[line]
219 new_obj = getattr(pointer, line)
220 self.parents.append(new_obj)
221 self.path.append(str(line))
224 def do_pwd(self, line):
225 """Shows the current path.
227 This duplicates the prompt functionality, but it's reasonable to
231 print "/" + "/".join(self.path)
234 def complete_cat(self, text, line, begidx, endidx):
235 """Completion for the cat command.
238 pointer = self.parents[-1]
239 _, entries = self._get_entries(pointer)
240 matches = [name for name in entries if name.startswith(text)]
243 def do_cat(self, line):
244 """Shows the contents of the given file.
246 This will display the contents of the given file, which must be a
247 child of the current path (as shows by `ls`).
250 pointer = self.parents[-1]
251 _, entries = self._get_entries(pointer)
252 if line not in entries:
253 print "No such entry"
256 if isinstance(pointer, (dict, list, tuple)):
257 if isinstance(pointer, (list, tuple)):
261 val = getattr(pointer, line)
265 def do_verify(self, line):
266 """Verify the configuration.
268 This verifies the contents of the configuration file (and not the
269 in-memory data, as every modify operation automatically saves the
273 vdata = self.cfg.VerifyConfig()
275 print "Validation failed. Errors:"
280 def do_save(self, line):
281 """Saves the configuration data.
283 Note that is redundant (all modify operations automatically save
284 the data), but it is good to use it as in the future that could
288 if self.cfg.VerifyConfig():
289 print "Config data does not validate, refusing to save."
291 self.cfg._WriteConfig() # pylint: disable=W0212
293 def do_rm(self, line):
294 """Removes an instance or a node.
296 This function works only on instances or nodes. You must be in
297 either `/nodes` or `/instances` and give a valid argument.
300 pointer = self.parents[-1]
301 data = self.cfg._config_data # pylint: disable=W0212
302 if pointer not in (data.instances, data.nodes):
303 print "Can only delete instances and nodes"
305 if pointer == data.instances:
306 if line in data.instances:
307 self.cfg.RemoveInstance(line)
309 print "Invalid instance name"
311 if line in data.nodes:
312 self.cfg.RemoveNode(line)
314 print "Invalid node name"
318 """Exit the application.
326 """Exit the application.
333 class Error(Exception):
334 """Generic exception"""
339 """Parses the command line options.
341 In case of command line errors, it will show the usage and exit the
344 @return: a tuple (options, args), as returned by OptionParser.parse_args
347 parser = optparse.OptionParser()
349 options, args = parser.parse_args()
355 """Application entry point.
358 _, args = ParseOptions()
363 shell = ConfigShell(cfg_file=cfg_file)
367 if __name__ == "__main__":