| #!/usr/bin/python |
| # |
| |
| # Copyright (C) 2006, 2007 Google Inc. |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, but |
| # WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write to the Free Software |
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| # 02110-1301, USA. |
| |
| |
| """Tool to do manual changes to the config file. |
| |
| """ |
| |
| # functions in this module need to have a given name structure, so: |
| # pylint: disable=C0103 |
| |
| |
| import optparse |
| import cmd |
| |
| try: |
| import readline |
| _wd = readline.get_completer_delims() |
| _wd = _wd.replace("-", "") |
| readline.set_completer_delims(_wd) |
| del _wd |
| except ImportError: |
| pass |
| |
| from ganeti import errors |
| from ganeti import config |
| from ganeti import objects |
| |
| |
| class ConfigShell(cmd.Cmd): |
| """Command tool for editing the config file. |
| |
| Note that although we don't do saves after remove, the current |
| ConfigWriter code does that; so we can't prevent someone from |
| actually breaking the config with this tool. It's the users' |
| responsibility to know what they're doing. |
| |
| """ |
| # all do_/complete_* functions follow the same API |
| # pylint: disable=W0613 |
| prompt = "(/) " |
| |
| def __init__(self, cfg_file=None): |
| """Constructor for the ConfigShell object. |
| |
| The optional cfg_file argument will be used to load a config file |
| at startup. |
| |
| """ |
| cmd.Cmd.__init__(self) |
| self.cfg = None |
| self.parents = [] |
| self.path = [] |
| if cfg_file: |
| self.do_load(cfg_file) |
| self.postcmd(False, "") |
| |
| def emptyline(self): |
| """Empty line handling. |
| |
| Note that the default will re-run the last command. We don't want |
| that, and just ignore the empty line. |
| |
| """ |
| return False |
| |
| @staticmethod |
| def _get_entries(obj): |
| """Computes the list of subdirs and files in the given object. |
| |
| This, depending on the passed object entry, look at each logical |
| child of the object and decides if it's a container or a simple |
| object. Based on this, it computes the list of subdir and files. |
| |
| """ |
| dirs = [] |
| entries = [] |
| if isinstance(obj, objects.ConfigObject): |
| for name in obj.GetAllSlots(): |
| child = getattr(obj, name, None) |
| if isinstance(child, (list, dict, tuple, objects.ConfigObject)): |
| dirs.append(name) |
| else: |
| entries.append(name) |
| elif isinstance(obj, (list, tuple)): |
| for idx, child in enumerate(obj): |
| if isinstance(child, (list, dict, tuple, objects.ConfigObject)): |
| dirs.append(str(idx)) |
| else: |
| entries.append(str(idx)) |
| elif isinstance(obj, dict): |
| dirs = obj.keys() |
| |
| return dirs, entries |
| |
| def precmd(self, line): |
| """Precmd hook to prevent commands in invalid states. |
| |
| This will prevent everything except load and quit when no |
| configuration is loaded. |
| |
| """ |
| if line.startswith("load") or line == "EOF" or line == "quit": |
| return line |
| if not self.parents or self.cfg is None: |
| print "No config data loaded" |
| return "" |
| return line |
| |
| def postcmd(self, stop, line): |
| """Postcmd hook to update the prompt. |
| |
| We show the current location in the prompt and this function is |
| used to update it; this is only needed after cd and load, but we |
| update it anyway. |
| |
| """ |
| if self.cfg is None: |
| self.prompt = "(#no config) " |
| else: |
| self.prompt = "(/%s) " % ("/".join(self.path),) |
| return stop |
| |
| def do_load(self, line): |
| """Load function. |
| |
| Syntax: load [/path/to/config/file] |
| |
| This will load a new configuration, discarding any existing data |
| (if any). If no argument has been passed, it will use the default |
| config file location. |
| |
| """ |
| if line: |
| arg = line |
| else: |
| arg = None |
| try: |
| self.cfg = config.ConfigWriter(cfg_file=arg, offline=True) |
| self.parents = [self.cfg._config_data] # pylint: disable=W0212 |
| self.path = [] |
| except errors.ConfigurationError, err: |
| print "Error: %s" % str(err) |
| return False |
| |
| def do_ls(self, line): |
| """List the current entry. |
| |
| This will show directories with a slash appended and files |
| normally. |
| |
| """ |
| dirs, entries = self._get_entries(self.parents[-1]) |
| for i in dirs: |
| print i + "/" |
| for i in entries: |
| print i |
| return False |
| |
| def complete_cd(self, text, line, begidx, endidx): |
| """Completion function for the cd command. |
| |
| """ |
| pointer = self.parents[-1] |
| dirs, _ = self._get_entries(pointer) |
| matches = [str(name) for name in dirs if name.startswith(text)] |
| return matches |
| |
| def do_cd(self, line): |
| """Changes the current path. |
| |
| Valid arguments: either .., /, "" (no argument) or a child of the current |
| object. |
| |
| """ |
| if line == "..": |
| if self.path: |
| self.path.pop() |
| self.parents.pop() |
| return False |
| else: |
| print "Already at top level" |
| return False |
| elif len(line) == 0 or line == "/": |
| self.parents = self.parents[0:1] |
| self.path = [] |
| return False |
| |
| pointer = self.parents[-1] |
| dirs, _ = self._get_entries(pointer) |
| |
| if line not in dirs: |
| print "No such child" |
| return False |
| if isinstance(pointer, (dict, list, tuple)): |
| if isinstance(pointer, (list, tuple)): |
| line = int(line) |
| new_obj = pointer[line] |
| else: |
| new_obj = getattr(pointer, line) |
| self.parents.append(new_obj) |
| self.path.append(str(line)) |
| return False |
| |
| def do_pwd(self, line): |
| """Shows the current path. |
| |
| This duplicates the prompt functionality, but it's reasonable to |
| have. |
| |
| """ |
| print "/" + "/".join(self.path) |
| return False |
| |
| def complete_cat(self, text, line, begidx, endidx): |
| """Completion for the cat command. |
| |
| """ |
| pointer = self.parents[-1] |
| _, entries = self._get_entries(pointer) |
| matches = [name for name in entries if name.startswith(text)] |
| return matches |
| |
| def do_cat(self, line): |
| """Shows the contents of the given file. |
| |
| This will display the contents of the given file, which must be a |
| child of the current path (as shows by `ls`). |
| |
| """ |
| pointer = self.parents[-1] |
| _, entries = self._get_entries(pointer) |
| if line not in entries: |
| print "No such entry" |
| return False |
| |
| if isinstance(pointer, (dict, list, tuple)): |
| if isinstance(pointer, (list, tuple)): |
| line = int(line) |
| val = pointer[line] |
| else: |
| val = getattr(pointer, line) |
| print val |
| return False |
| |
| def do_verify(self, line): |
| """Verify the configuration. |
| |
| This verifies the contents of the configuration file (and not the |
| in-memory data, as every modify operation automatically saves the |
| file). |
| |
| """ |
| vdata = self.cfg.VerifyConfig() |
| if vdata: |
| print "Validation failed. Errors:" |
| for text in vdata: |
| print text |
| return False |
| |
| def do_save(self, line): |
| """Saves the configuration data. |
| |
| Note that is redundant (all modify operations automatically save |
| the data), but it is good to use it as in the future that could |
| change. |
| |
| """ |
| if self.cfg.VerifyConfig(): |
| print "Config data does not validate, refusing to save." |
| return False |
| self.cfg._WriteConfig() # pylint: disable=W0212 |
| |
| def do_rm(self, line): |
| """Removes an instance or a node. |
| |
| This function works only on instances or nodes. You must be in |
| either `/nodes` or `/instances` and give a valid argument. |
| |
| """ |
| pointer = self.parents[-1] |
| data = self.cfg._config_data # pylint: disable=W0212 |
| if pointer not in (data.instances, data.nodes): |
| print "Can only delete instances and nodes" |
| return False |
| if pointer == data.instances: |
| if line in data.instances: |
| self.cfg.RemoveInstance(line) |
| else: |
| print "Invalid instance name" |
| else: |
| if line in data.nodes: |
| self.cfg.RemoveNode(line) |
| else: |
| print "Invalid node name" |
| |
| @staticmethod |
| def do_EOF(line): |
| """Exit the application. |
| |
| """ |
| print |
| return True |
| |
| @staticmethod |
| def do_quit(line): |
| """Exit the application. |
| |
| """ |
| print |
| return True |
| |
| |
| class Error(Exception): |
| """Generic exception""" |
| pass |
| |
| |
| def ParseOptions(): |
| """Parses the command line options. |
| |
| In case of command line errors, it will show the usage and exit the |
| program. |
| |
| @return: a tuple (options, args), as returned by OptionParser.parse_args |
| |
| """ |
| parser = optparse.OptionParser() |
| |
| options, args = parser.parse_args() |
| |
| return options, args |
| |
| |
| def main(): |
| """Application entry point. |
| |
| """ |
| _, args = ParseOptions() |
| if args: |
| cfg_file = args[0] |
| else: |
| cfg_file = None |
| shell = ConfigShell(cfg_file=cfg_file) |
| shell.cmdloop() |
| |
| |
| if __name__ == "__main__": |
| main() |