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 for name in obj.GetAllSlots():
97 child = getattr(obj, name, None)
98 if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
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))
107 entries.append(str(idx))
108 elif isinstance(obj, dict):
113 def precmd(self, line):
114 """Precmd hook to prevent commands in invalid states.
116 This will prevent everything except load and quit when no
117 configuration is loaded.
120 if line.startswith("load") or line == "EOF" or line == "quit":
122 if not self.parents or self.cfg is None:
123 print "No config data loaded"
127 def postcmd(self, stop, line):
128 """Postcmd hook to update the prompt.
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
136 self.prompt = "(#no config) "
138 self.prompt = "(/%s) " % ("/".join(self.path),)
141 def do_load(self, line):
144 Syntax: load [/path/to/config/file]
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.
156 self.cfg = config.ConfigWriter(cfg_file=arg, offline=True)
157 self.parents = [self.cfg._config_data] # pylint: disable=W0212
159 except errors.ConfigurationError, err:
160 print "Error: %s" % str(err)
163 def do_ls(self, line):
164 """List the current entry.
166 This will show directories with a slash appended and files
170 dirs, entries = self._get_entries(self.parents[-1])
177 def complete_cd(self, text, line, begidx, endidx):
178 """Completion function for the cd command.
181 pointer = self.parents[-1]
182 dirs, _ = self._get_entries(pointer)
183 matches = [str(name) for name in dirs if name.startswith(text)]
186 def do_cd(self, line):
187 """Changes the current path.
189 Valid arguments: either .., /, "" (no argument) or a child of the current
199 print "Already at top level"
201 elif len(line) == 0 or line == "/":
202 self.parents = self.parents[0:1]
206 pointer = self.parents[-1]
207 dirs, _ = self._get_entries(pointer)
210 print "No such child"
212 if isinstance(pointer, (dict, list, tuple)):
213 if isinstance(pointer, (list, tuple)):
215 new_obj = pointer[line]
217 new_obj = getattr(pointer, line)
218 self.parents.append(new_obj)
219 self.path.append(str(line))
222 def do_pwd(self, line):
223 """Shows the current path.
225 This duplicates the prompt functionality, but it's reasonable to
229 print "/" + "/".join(self.path)
232 def complete_cat(self, text, line, begidx, endidx):
233 """Completion for the cat command.
236 pointer = self.parents[-1]
237 _, entries = self._get_entries(pointer)
238 matches = [name for name in entries if name.startswith(text)]
241 def do_cat(self, line):
242 """Shows the contents of the given file.
244 This will display the contents of the given file, which must be a
245 child of the current path (as shows by `ls`).
248 pointer = self.parents[-1]
249 _, entries = self._get_entries(pointer)
250 if line not in entries:
251 print "No such entry"
254 if isinstance(pointer, (dict, list, tuple)):
255 if isinstance(pointer, (list, tuple)):
259 val = getattr(pointer, line)
263 def do_verify(self, line):
264 """Verify the configuration.
266 This verifies the contents of the configuration file (and not the
267 in-memory data, as every modify operation automatically saves the
271 vdata = self.cfg.VerifyConfig()
273 print "Validation failed. Errors:"
278 def do_save(self, line):
279 """Saves the configuration data.
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
286 if self.cfg.VerifyConfig():
287 print "Config data does not validate, refusing to save."
289 self.cfg._WriteConfig() # pylint: disable=W0212
291 def do_rm(self, line):
292 """Removes an instance or a node.
294 This function works only on instances or nodes. You must be in
295 either `/nodes` or `/instances` and give a valid argument.
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"
303 if pointer == data.instances:
304 if line in data.instances:
305 self.cfg.RemoveInstance(line)
307 print "Invalid instance name"
309 if line in data.nodes:
310 self.cfg.RemoveNode(line)
312 print "Invalid node name"
316 """Exit the application.
324 """Exit the application.
331 class Error(Exception):
332 """Generic exception"""
337 """Parses the command line options.
339 In case of command line errors, it will show the usage and exit the
342 @return: a tuple (options, args), as returned by OptionParser.parse_args
345 parser = optparse.OptionParser()
347 options, args = parser.parse_args()
353 """Application entry point.
356 _, args = ParseOptions()
361 shell = ConfigShell(cfg_file=cfg_file)
365 if __name__ == "__main__":