X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=Shell.py;h=b755648973c9d35ab07503b36cf46a04bc5896f0;hb=444fb00ab548e2c35d9d933f38977cde212ce7d3;hp=83c0d32d5679004b1c6335630af70c5e6a7eadc2;hpb=a96bf480a19a552b7d7f69513c5f476689a5dca7;p=plcapi.git diff --git a/Shell.py b/Shell.py index 83c0d32..b755648 100755 --- a/Shell.py +++ b/Shell.py @@ -5,7 +5,7 @@ # Mark Huang # Copyright (C) 2005 The Trustees of Princeton University # -# $Id: Shell.py,v 1.3 2006/09/08 00:29:34 mlhuang Exp $ +# $Id: Shell.py,v 1.17 2006/12/13 22:29:28 mlhuang Exp $ # import os, sys @@ -21,191 +21,11 @@ from PLC.Parameter import Mixed from PLC.Auth import Auth from PLC.Config import Config from PLC.Method import Method +from PLC.PyCurl import PyCurlTransport import PLC.Methods -# Defaults -config = "/etc/planetlab/plc_config" -url = None -method = None -user = None -password = None -role = None - -def usage(): - print "Usage: %s [OPTION]..." % sys.argv[0] - print "Options:" - print " -f, --config=FILE PLC configuration file" - print " -h, --url=URL API URL" - print " -m, --method=METHOD API authentication method" - print " -u, --user=EMAIL API user name" - print " -p, --password=STRING API password" - print " -r, --role=ROLE API role" - print " -x, --xmlrpc Use XML-RPC interface" - print " --help This message" - sys.exit(1) - -# Get options -try: - (opts, argv) = getopt.getopt(sys.argv[1:], - "f:h:m:u:p:r:x", - ["config=", "cfg=", "file=", - "host=", - "method=", - "username=", "user=", - "password=", "pass=", "authstring=", - "role=", - "xmlrpc", - "help"]) -except getopt.GetoptError, err: - print "Error: " + err.msg - usage() - -for (opt, optval) in opts: - if opt == "-f" or opt == "--config" or opt == "--cfg" or opt == "--file": - config = optval - elif opt == "-h" or opt == "--host" or opt == "--url": - url = optval - elif opt == "-m" or opt == "--method": - method = optval - elif opt == "-u" or opt == "--username" or opt == "--user": - user = optval - elif opt == "-p" or opt == "--password" or opt == "--pass" or opt == "--authstring": - password = optval - elif opt == "-r" or opt == "--role": - role = optval - elif opt == "--help": - usage() - -try: - # If any XML-RPC options have been specified, do not try - # connecting directly to the DB. - if opts: - raise Exception - - # Otherwise, first try connecting directly to the DB. If this - # fails, try connecting to the API server via XML-RPC. - api = PLCAPI(config) - config = api.config - server = None -except: - # Try connecting to the API server via XML-RPC - api = PLCAPI(None) - config = Config(config) - - if url is None: - if int(config.PLC_API_PORT) == 443: - url = "https://" - else: - url = "http://" - url += config.PLC_API_HOST + \ - ":" + str(config.PLC_API_PORT) + \ - "/" + config.PLC_API_PATH + "/" - - server = xmlrpclib.ServerProxy(url) - -# Default is to use capability authentication -if (method, user, password) == (None, None, None): - method = "capability" - -if method == "capability": - if user is None: - user = config.PLC_API_MAINTENANCE_USER - if password is None: - password = config.PLC_API_MAINTENANCE_PASSWORD - if role is None: - role = "admin" -elif method is None: - method = "password" - -if role == "anonymous" or method == "anonymous": - auth = {'AuthMethod': "anonymous"} -else: - if role is None: - print "Error: must specify a role with -r" - usage() - - if user is None: - print "Error: must specify a username with -u" - usage() - - if password is None: - try: - password = getpass.getpass() - except (EOFError, KeyboardInterrupt): - print - sys.exit(0) - - auth = {'AuthMethod': method, - 'Username': user, - 'AuthString': password, - 'Role': role} - -class Callable: - """ - Wrapper to call a method either directly or remotely. Initialize - with no arguments to use as a dummy class to support tab - completion of API methods with dots in their names (e.g., - system.listMethods). - """ - - def __init__(self, method = None): - self.name = method - - if method is not None: - # Figure out if the function requires an authentication - # structure as its first argument. - self.auth = False - - try: - func = api.callable(method) - if func.accepts and \ - (isinstance(func.accepts[0], Auth) or \ - (isinstance(func.accepts[0], Mixed) and \ - filter(lambda param: isinstance(param, Auth), func.accepts[0]))): - self.auth = True - except: - # XXX Ignore undefined methods for now - pass - - if server is not None: - self.func = getattr(server, method) - else: - self.func = func - - def __call__(self, *args): - """ - Automagically add the authentication structure if the function - requires it and it has not been specified. - """ - - if self.auth and \ - (not args or not isinstance(args[0], dict) or not args[0].has_key('AuthMethod')): - return self.func(auth, *args) - else: - return self.func(*args) - -if server is not None: - methods = server.system.listMethods() -else: - methods = api.call(None, "system.listMethods") - -# Define all methods in the global namespace to support tab completion -for method in methods: - paths = method.split(".") - if len(paths) > 1: - first = paths.pop(0) - if first not in globals(): - globals()[first] = Callable() - obj = globals()[first] - for path in paths: - if not hasattr(obj, path): - if path == paths[-1]: - setattr(obj, path, Callable(method)) - else: - setattr(obj, path, Callable()) - obj = getattr(obj, path) - else: - globals()[method] = Callable(method) +# the list of globals formerly defined by Shell.py before it was made a class +former_globals = ['api','auth','config','begin','commit','calls'] pyhelp = help def help(thing): @@ -214,7 +34,7 @@ def help(thing): """ # help(method) - if isinstance(thing, Callable) and thing.name is not None: + if isinstance(thing, Shell.Callable) and thing.name is not None: pydoc.pager(system.methodHelp(thing.name)) return @@ -225,87 +45,385 @@ def help(thing): # help(...) pyhelp(thing) -# If a file is specified -if argv: - execfile(argv[0]) - sys.exit(0) - -# Otherwise, create an interactive shell environment - -if server is None: - print "PlanetLab Central Direct API Access" - prompt = "" -elif auth['AuthMethod'] == "anonymous": - prompt = "[anonymous]" - print "Connected anonymously" -else: - prompt = "[%s %s]" % (auth['Username'], auth['Role']) - print "%s connected as %s using %s authentication" % \ - (auth['Username'], auth['Role'], auth['AuthMethod']) -print 'Type "system.listMethods()" or "help(method)" for more information.' - -# Readline and tab completion support -import atexit -import readline -import rlcompleter - -# Load command history -history_path = os.path.join(os.environ["HOME"], ".plcapi_history") -try: - file(history_path, 'a').close() - readline.read_history_file(history_path) - atexit.register(readline.write_history_file, history_path) -except IOError: - pass - -# Enable tab completion -readline.parse_and_bind("tab: complete") - -try: - while True: - command = "" - while True: - # Get line +#################### +class Shell: + + def __init__ (self,argv,config=None): + + # Defaults + if config is not None: + self.config=config + else: + # support running on non-myplc boxes + default_config_file = "/etc/planetlab/plc_config" try: - if command == "": - sep = ">>> " - else: - sep = "... " - line = raw_input(prompt + sep) - # Ctrl-C - except KeyboardInterrupt: - command = "" - print - break + open (default_config_file).close() + except: + default_config_file="/dev/null" + self.config = default_config_file + self.url = None + self.method = None + self.user = None + self.password = None + self.role = None + self.xmlrpc = False + self.server = None + self.cacert = None + + # More convenient multicall support + self.multi = False + self.calls = [] + self.argv = argv + + def init_from_argv (self): - # Build up multi-line command - command += line + try: + (opts, argv) = getopt.getopt(self.argv[1:], + "f:h:m:u:p:r:x", + ["config=", "cfg=", "file=", + "host=","url=", + "method=", + "username=", "user=", + "password=", "pass=", "authstring=", + "role=", + "xmlrpc", + "cacert=", + "help"]) + except getopt.GetoptError, err: + print "Error: ", err.msg + self.usage(self.argv) + + for (opt, optval) in opts: + if opt == "-f" or opt == "--config" or opt == "--cfg" or opt == "--file": + self.config = optval + elif opt == "-h" or opt == "--host" or opt == "--url": + self.url = optval + elif opt == "-m" or opt == "--method": + self.method = optval + elif opt == "-u" or opt == "--username" or opt == "--user": + self.user = optval + elif opt == "-p" or opt == "--password" or opt == "--pass" or opt == "--authstring": + self.password = optval + elif opt == "-r" or opt == "--role": + self.role = optval + elif opt == "-x" or opt == "--xmlrpc": + self.xmlrpc = True + elif opt == "--cacert": + self.cacert = optval + elif opt == "--help": + self.usage(self.argv) + + def usage(self,argv): + print "Usage: %s [OPTION]..." % argv[0] + print "Options:" + print " -f, --config=FILE PLC configuration file" + print " -h, --url=URL API URL" + print " -m, --method=METHOD API authentication method" + print " -u, --user=EMAIL API user name" + print " -p, --password=STRING API password" + print " -r, --role=ROLE API role" + print " -x, --xmlrpc Use XML-RPC interface" + print " --cacert=CACERT API SSL certificate" + print " --help This message" + sys.exit(1) + + def init_connection(self): + + # Append PLC to the system path + sys.path.append(os.path.dirname(os.path.realpath(self.argv[0]))) + + try: + # If any XML-RPC options have been specified, do not try + # connecting directly to the DB. + if (self.url, self.method, self.user, self.password, self.role, self.cacert, self.xmlrpc) != \ + (None, None, None, None, None, None, False): + raise Exception + + # Otherwise, first try connecting directly to the DB. If this + # fails, try connecting to the API server via XML-RPC. + self.api = PLCAPI(self.config) + self.config = self.api.config + self.server = None + except: + # Try connecting to the API server via XML-RPC + self.api = PLCAPI(None) + self.config = Config(self.config) + + if self.url is None: + if int(self.config.PLC_API_PORT) == 443: + self.url = "https://" + else: + self.url = "http://" + self.url += self.config.PLC_API_HOST + \ + ":" + str(self.config.PLC_API_PORT) + \ + "/" + self.config.PLC_API_PATH + "/" + + if self.cacert is None: + self.cacert = self.config.PLC_API_CA_SSL_CRT + + self.server = xmlrpclib.ServerProxy(self.url, PyCurlTransport(self.url, self.cacert), allow_none = 1) + + # Default is to use capability authentication + if (self.method, self.user, self.password) == (None, None, None): + self.method = "capability" + + if self.method == "capability": + if self.user is None: + self.user = self.config.PLC_API_MAINTENANCE_USER + if self.password is None: + self.password = self.config.PLC_API_MAINTENANCE_PASSWORD + if self.role is None: + self.role = "admin" + elif self.method is None: + self.method = "password" + + if self.role == "anonymous" or self.method == "anonymous": + self.auth = {'AuthMethod': "anonymous"} + else: + if self.user is None: + print "Error: must specify a username with -u" + self.usage() + + if self.password is None: + try: + self.password = getpass.getpass() + except (EOFError, KeyboardInterrupt): + print + sys.exit(0) + + self.auth = {'AuthMethod': self.method, + 'Username': self.user, + 'AuthString': self.password} + + if self.role is not None: + self.auth['Role'] = self.role + + def begin(self): + if self.calls: + raise Exception, "multicall already in progress" + + self.multi = True + + def commit(self): + + if calls: + ret = [] + self.multi = False + results = system.multicall(calls) + for result in results: + if type(result) == type({}): + raise xmlrpclib.Fault(item['faultCode'], item['faultString']) + elif type(result) == type([]): + ret.append(result[0]) + else: + raise ValueError, "unexpected type in multicall result" + else: + ret = None - # Blank line or first line does not end in : - if line == "" or (command == line and line[-1] != ':'): - break + self.calls = [] + self.multi = False - command += os.linesep + return ret - # Blank line - if command == "": - continue - # Quit - elif command in ["q", "quit", "exit"]: - break + class Callable: + """ + Wrapper to call a method either directly or remotely. Initialize + with no arguments to use as a dummy class to support tab + completion of API methods with dots in their names (e.g., + system.listMethods). + """ + def __init__(self, shell, method = None): + self.shell=shell + self.name = method + + if method is not None: + # Figure out if the function requires an authentication + # structure as its first argument. + self.auth = False + + try: + func = shell.api.callable(method) + if func.accepts and \ + (isinstance(func.accepts[0], Auth) or \ + (isinstance(func.accepts[0], Mixed) and \ + filter(lambda param: isinstance(param, Auth), func.accepts[0]))): + self.auth = True + except: + traceback.print_exc() + # XXX Ignore undefined methods for now + pass + + if shell.server is not None: + self.func = getattr(shell.server, method) + else: + self.func = func + + def __call__(self, *args, **kwds): + """ + Automagically add the authentication structure if the function + requires it and it has not been specified. + """ + + if self.auth and \ + (not args or not isinstance(args[0], dict) or \ + (not args[0].has_key('AuthMethod') and \ + not args[0].has_key('session'))): + args = (self.shell.auth,) + args + + if self.shell.multi: + self.shell.calls.append({'methodName': self.name, 'params': list(args)}) + return None + else: + return self.func(*args, **kwds) + + def init_methods (self): + # makes methods defined on self + for method in PLC.Methods.methods: + # ignore path-defined methods for now + if "." not in method: + setattr(self,method,Shell.Callable(self,method)) + + def init_globals (self): + # Define all methods in the global namespace to support tab completion + for method in PLC.Methods.methods: + paths = method.split(".") + if len(paths) > 1: + first = paths.pop(0) + if first not in globals(): + globals()[first] = Shell.Callable(self) + obj = globals()[first] + for path in paths: + if not hasattr(obj, path): + if path == paths[-1]: + setattr(obj, path, Shell.Callable(self,method)) + else: + setattr(obj, path, Shell.Callable(self)) + obj = getattr(obj, path) + else: + globals()[method] = Shell.Callable(self,method) + # Other stuff to be made visible in globals() + for slot in former_globals: + #print 'Inserting global',slot + globals()[slot] = getattr(self,slot) + + def run_script (self): + # Pop us off the argument stack + self.argv.pop(0) + execfile(self.argv[0],globals(),globals()) + sys.exit(0) + + def show_config (self, verbose=False): + if self.server is None: + print "PlanetLab Central Direct API Access" + self.prompt = "" + elif self.auth['AuthMethod'] == "anonymous": + self.prompt = "[anonymous]" + print "Connected anonymously" + else: + self.prompt = "[%s]" % self.auth['Username'] + print "%s connected using %s authentication" % \ + (self.auth['Username'], self.auth['AuthMethod']) + + if verbose: + print 'url',self.url + print 'server',self.server + print 'method',self.method + print 'user',self.user, + print 'password',self.password + print 'role',self.role, + print 'xmlrpc',self.xmlrpc, + print 'multi',self.multi, + print 'calls',self.calls + + def run_interactive (self): + # Readline and tab completion support + import atexit + import readline + import rlcompleter + + print 'Type "system.listMethods()" or "help(method)" for more information.' + # Load command history + history_path = os.path.join(os.environ["HOME"], ".plcapi_history") try: - try: - # Try evaluating as an expression and printing the result - result = eval(command) - if result is not None: - print result - except: - # Fall back to executing as a statement - exec command - except Exception, err: - traceback.print_exc() - -except EOFError: - print - pass + file(history_path, 'a').close() + readline.read_history_file(history_path) + atexit.register(readline.write_history_file, history_path) + except IOError: + pass + + # Enable tab completion + readline.parse_and_bind("tab: complete") + + try: + while True: + command = "" + while True: + # Get line + try: + if command == "": + sep = ">>> " + else: + sep = "... " + line = raw_input(self.prompt + sep) + # Ctrl-C + except KeyboardInterrupt: + command = "" + print + break + + # Build up multi-line command + command += line + + # Blank line or first line does not end in : + if line == "" or (command == line and line[-1] != ':'): + break + + command += os.linesep + + # Blank line + if command == "": + continue + # Quit + elif command in ["q", "quit", "exit"]: + break + + try: + try: + # Try evaluating as an expression and printing the result + result = eval(command) + if result is not None: + print result + except SyntaxError: + # Fall back to executing as a statement + exec command + except Exception, err: + traceback.print_exc() + + except EOFError: + print + pass + + # support former behaviour + def run (self): + if len(self.argv) < 2 or not os.path.exists(self.argv[1]): + # Parse options if called interactively + self.init_from_argv() + self.init_connection() + self.init_globals() + # If called by a script + if len(sys.argv) > 1 and os.path.exists(sys.argv[1]): +# self.show_config() + self.run_script() + else: + self.show_config() + self.run_interactive() + + # does not run anything, support for multi-plc, see e.g. TestPeers.py + def init(self): + self.init_from_argv() + self.init_connection() + self.init_methods() + +if __name__ == '__main__': + Shell(sys.argv).run()