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.parents = [self.cfg._config_data]
157 except errors.ConfigurationError, err:
158 print "Error: %s" % str(err)
161 def do_ls(self, line):
162 """List the current entry.
164 This will show directories with a slash appended and files
168 dirs, entries = self._get_entries(self.parents[-1])
175 def complete_cd(self, text, line, begidx, endidx):
176 """Completion function for the cd command.
179 pointer = self.parents[-1]
180 dirs, entries = self._get_entries(pointer)
181 matches = [str(name) for name in dirs if name.startswith(text)]
184 def do_cd(self, line):
185 """Changes the current path.
187 Valid arguments: either .., /, "" (no argument) or a child of the current
197 print "Already at top level"
199 elif len(line) == 0 or line == "/":
200 self.parents = self.parents[0:1]
204 pointer = self.parents[-1]
205 dirs, entries = self._get_entries(pointer)
208 print "No such child"
210 if isinstance(pointer, (dict, list, tuple)):
211 if isinstance(pointer, (list, tuple)):
213 new_obj = pointer[line]
215 new_obj = getattr(pointer, line)
216 self.parents.append(new_obj)
217 self.path.append(str(line))
220 def do_pwd(self, line):
221 """Shows the current path.
223 This duplicates the prompt functionality, but it's reasonable to
227 print "/" + "/".join(self.path)
230 def complete_cat(self, text, line, begidx, endidx):
231 """Completion for the cat command.
234 pointer = self.parents[-1]
235 dirs, entries = self._get_entries(pointer)
236 matches = [name for name in entries if name.startswith(text)]
239 def do_cat(self, line):
240 """Shows the contents of the given file.
242 This will display the contents of the given file, which must be a
243 child of the current path (as shows by `ls`).
246 pointer = self.parents[-1]
247 dirs, entries = self._get_entries(pointer)
248 if line not in entries:
249 print "No such entry"
252 if isinstance(pointer, (dict, list, tuple)):
253 if isinstance(pointer, (list, tuple)):
257 val = getattr(pointer, line)
261 def do_verify(self, line):
262 """Verify the configuration.
264 This verifies the contents of the configuration file (and not the
265 in-memory data, as every modify operation automatically saves the
269 vdata = self.cfg.VerifyConfig()
271 print "Validation failed. Errors:"
276 def do_save(self, line):
277 """Saves the configuration data.
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
284 if self.cfg.VerifyConfig():
285 print "Config data does not validate, refusing to save."
287 self.cfg._WriteConfig()
289 def do_rm(self, line):
290 """Removes an instance or a node.
292 This function works only on instances or nodes. You must be in
293 either `/nodes` or `/instances` and give a valid argument.
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"
301 if pointer == data.instances:
302 if line in data.instances:
303 self.cfg.RemoveInstance(line)
305 print "Invalid instance name"
307 if line in data.nodes:
308 self.cfg.RemoveNode(line)
310 print "Invalid node name"
312 def do_EOF(self, line):
313 """Exit the application.
319 def do_quit(self, line):
320 """Exit the application.
327 class Error(Exception):
328 """Generic exception"""
333 """Parses the command line options.
335 In case of command line errors, it will show the usage and exit the
339 (options, args), as returned by OptionParser.parse_args
342 parser = optparse.OptionParser()
344 options, args = parser.parse_args()
350 """Application entry point.
352 This is just a wrapper over BootStrap, to handle our own exceptions.
354 options, args = ParseOptions()
359 shell = ConfigShell(cfg_file=cfg_file)
363 if __name__ == "__main__":