- add XML-RPC support
authorMark Huang <mlhuang@cs.princeton.edu>
Fri, 8 Sep 2006 00:29:34 +0000 (00:29 +0000)
committerMark Huang <mlhuang@cs.princeton.edu>
Fri, 8 Sep 2006 00:29:34 +0000 (00:29 +0000)
- now more like plcsh; specifying authentication parameter as first
  argument is optional (automagically added if required and not
  specified)

Shell.py

index b76e869..f2f4592 100755 (executable)
--- a/Shell.py
+++ b/Shell.py
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2005 The Trustees of Princeton University
 #
-# $Id: Shell.py,v 1.1 2006/09/06 15:33:59 mlhuang Exp $
+# $Id: Shell.py,v 1.2 2006/09/06 19:17:25 mlhuang Exp $
 #
 
 import os, sys
 import traceback
 import getopt
 import pydoc
+import pg
+import xmlrpclib
+import getpass
 
 from PLC.API import PLCAPI
+from PLC.Parameter import Mixed
+from PLC.Auth import Auth
+from PLC.Config import Config
 from PLC.Method import Method
+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 (default: %s)" % config
-    print "     -h, --help              This message"
+    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 "     --help                  This message"
     sys.exit(1)
 
 # Get options
 try:
-    (opts, argv) = getopt.getopt(sys.argv[1:], "f:h", ["config=", "help"])
+    (opts, argv) = getopt.getopt(sys.argv[1:],
+                                 "f:h:m:u:p:r:",
+                                 ["config=", "cfg=", "file=",
+                                  "host=",
+                                  "method=",
+                                  "username=", "user=",
+                                  "password=", "pass=", "authstring=",
+                                  "role=",
+                                  "help"])
 except getopt.GetoptError, err:
     print "Error: " + err.msg
     usage()
 
 for (opt, optval) in opts:
-    if opt == "-f" or opt == "--config":
+    if opt == "-f" or opt == "--config" or opt == "--cfg" or opt == "--file":
         config = optval
-    elif opt == "-h" or opt == "--help":
+    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()
 
-api = PLCAPI(config)
+try:
+    # Try connecting directly to the DB
+    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 + \
+               ":8000" + 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': "capability",
-        'Username': api.config.PLC_API_MAINTENANCE_USER,
-        'AuthString': api.config.PLC_API_MAINTENANCE_PASSWORD,
-        'Role': "admin"}
+    auth = {'AuthMethod': method,
+            'Username': user,
+            'AuthString': password,
+            'Role': role}
 
-class Dummy:
+class Callable:
     """
-    Dummy class to support tab completion of API methods with dots in
-    their names (e.g., system.listMethods).
+    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).
     """
-    pass
-        
+
+    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.
+            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
+            else:
+                self.auth = False
+
+            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 api.methods:
+for method in methods:
     paths = method.split(".")
     if len(paths) > 1:
         first = paths.pop(0)
         if first not in globals():
-            globals()[first] = Dummy()
+            globals()[first] = Callable()
         obj = globals()[first]
         for path in paths:
             if not hasattr(obj, path):
                 if path == paths[-1]:
-                    setattr(obj, path, api.callable(method))
+                    setattr(obj, path, Callable(method))
                 else:
-                    setattr(obj, path, Dummy())
+                    setattr(obj, path, Callable())
             obj = getattr(obj, path)
     else:
-        globals()[method] = api.callable(method)
+        globals()[method] = Callable(method)
 
 pyhelp = help
 def help(thing):
@@ -77,10 +201,17 @@ def help(thing):
     Override builtin help() function to support calling help(method).
     """
 
-    if isinstance(thing, Method):
-        return pydoc.pager(thing.help())
-    else:
-        return pyhelp(thing)
+    # help(method)
+    if isinstance(thing, Callable) and thing.name is not None:
+        pydoc.pager(system.methodHelp(thing.name))
+        return
+
+    # help(help)
+    if thing == help:
+        thing = pyhelp
+
+    # help(...)
+    pyhelp(thing)
 
 # If a file is specified
 if argv:
@@ -89,8 +220,16 @@ if argv:
 
 # Otherwise, create an interactive shell environment
 
-print "PlanetLab Central Direct API Access"
-prompt = ""
+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