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.
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)
66 self.cfg = self.cluster_name = None
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:/%s) " % (self.cluster_name, "/".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 self.cluster_name = self.cfg.GetClusterName()
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, entries = 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, entries = 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 dirs, 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 dirs, 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()
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
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"
314 def do_EOF(self, line):
318 def do_quit(self, line):
319 """Exit the application.
326 class Error(Exception):
327 """Generic exception"""
332 """Parses the command line options.
334 In case of command line errors, it will show the usage and exit the
338 (options, args), as returned by OptionParser.parse_args
341 parser = optparse.OptionParser()
343 options, args = parser.parse_args()
349 """Application entry point.
351 This is just a wrapper over BootStrap, to handle our own exceptions.
353 options, args = ParseOptions()
358 shell = ConfigShell(cfg_file=cfg_file)
362 if __name__ == "__main__":