- add **kwds paramater to __call__()
[plcapi.git] / Shell.py
1 #!/usr/bin/python
2 #
3 # Interactive shell for testing PLCAPI
4 #
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2005 The Trustees of Princeton University
7 #
8 # $Id: Shell.py,v 1.5 2006/10/03 19:34:05 mlhuang Exp $
9 #
10
11 import os, sys
12 import traceback
13 import getopt
14 import pydoc
15 import pg
16 import xmlrpclib
17 import getpass
18
19 from PLC.API import PLCAPI
20 from PLC.Parameter import Mixed
21 from PLC.Auth import Auth
22 from PLC.Config import Config
23 from PLC.Method import Method
24 import PLC.Methods
25
26 # Defaults
27 config = "/etc/planetlab/plc_config"
28 url = None
29 method = None
30 user = None
31 password = None
32 role = None
33
34 def usage():
35     print "Usage: %s [OPTION]..." % sys.argv[0]
36     print "Options:"
37     print "     -f, --config=FILE       PLC configuration file"
38     print "     -h, --url=URL           API URL"
39     print "     -m, --method=METHOD     API authentication method"
40     print "     -u, --user=EMAIL        API user name"
41     print "     -p, --password=STRING   API password"
42     print "     -r, --role=ROLE         API role"
43     print "     -x, --xmlrpc            Use XML-RPC interface"
44     print "     --help                  This message"
45     sys.exit(1)
46
47 # Get options
48 try:
49     (opts, argv) = getopt.getopt(sys.argv[1:],
50                                  "f:h:m:u:p:r:x",
51                                  ["config=", "cfg=", "file=",
52                                   "host=",
53                                   "method=",
54                                   "username=", "user=",
55                                   "password=", "pass=", "authstring=",
56                                   "role=",
57                                   "xmlrpc",
58                                   "help"])
59 except getopt.GetoptError, err:
60     print "Error: " + err.msg
61     usage()
62
63 for (opt, optval) in opts:
64     if opt == "-f" or opt == "--config" or opt == "--cfg" or opt == "--file":
65         config = optval
66     elif opt == "-h" or opt == "--host" or opt == "--url":
67         url = optval
68     elif opt == "-m" or opt == "--method":
69         method = optval
70     elif opt == "-u" or opt == "--username" or opt == "--user":
71         user = optval
72     elif opt == "-p" or opt == "--password" or opt == "--pass" or opt == "--authstring":
73         password = optval
74     elif opt == "-r" or opt == "--role":
75         role = optval
76     elif opt == "--help":
77         usage()
78
79 try:
80     # If any XML-RPC options have been specified, do not try
81     # connecting directly to the DB.
82     if opts:
83         raise Exception
84         
85     # Otherwise, first try connecting directly to the DB. If this
86     # fails, try connecting to the API server via XML-RPC.
87     api = PLCAPI(config)
88     config = api.config
89     server = None
90 except:
91     # Try connecting to the API server via XML-RPC
92     api = PLCAPI(None)
93     config = Config(config)
94
95     if url is None:
96         if int(config.PLC_API_PORT) == 443:
97             url = "https://"
98         else:
99             url = "http://"
100         url += config.PLC_API_HOST + \
101                ":" + str(config.PLC_API_PORT) + \
102                "/" + config.PLC_API_PATH + "/"
103
104     server = xmlrpclib.ServerProxy(url, allow_none = 1)
105
106 # Default is to use capability authentication
107 if (method, user, password) == (None, None, None):
108     method = "capability"
109
110 if method == "capability":
111     if user is None:
112         user = config.PLC_API_MAINTENANCE_USER
113     if password is None:
114         password = config.PLC_API_MAINTENANCE_PASSWORD
115     if role is None:
116         role = "admin"
117 elif method is None:
118     method = "password"
119
120 if role == "anonymous" or method == "anonymous":
121     auth = {'AuthMethod': "anonymous"}
122 else:
123     if role is None:
124         print "Error: must specify a role with -r"
125         usage()
126
127     if user is None:
128         print "Error: must specify a username with -u"
129         usage()
130
131     if password is None:
132         try:
133             password = getpass.getpass()
134         except (EOFError, KeyboardInterrupt):
135             print
136             sys.exit(0)
137
138     auth = {'AuthMethod': method,
139             'Username': user,
140             'AuthString': password,
141             'Role': role}
142
143 class Callable:
144     """
145     Wrapper to call a method either directly or remotely. Initialize
146     with no arguments to use as a dummy class to support tab
147     completion of API methods with dots in their names (e.g.,
148     system.listMethods).
149     """
150
151     def __init__(self, method = None):
152         self.name = method
153
154         if method is not None:
155             # Figure out if the function requires an authentication
156             # structure as its first argument.
157             self.auth = False
158             func = api.callable(method)
159             
160             try:
161                 #func = api.callable(method)
162                 if func.accepts and \
163                    (isinstance(func.accepts[0], Auth) or \
164                     (isinstance(func.accepts[0], Mixed) and \
165                      filter(lambda param: isinstance(param, Auth), func.accepts[0]))):
166                     self.auth = True
167             except:
168                 # XXX Ignore undefined methods for now
169                 pass
170
171             if server is not None:
172                 self.func = getattr(server, method)
173             else:
174                 self.func = func
175
176     def __call__(self, *args, **kwds):
177         """
178         Automagically add the authentication structure if the function
179         requires it and it has not been specified.
180         """
181
182         if self.auth and \
183            (not args or not isinstance(args[0], dict) or not args[0].has_key('AuthMethod')):
184             return self.func(auth, *args, **kwds)
185         else:
186             return self.func(*args)
187
188 if server is not None:
189     methods = server.system.listMethods()
190 else:
191     methods = api.call(None, "system.listMethods")
192
193 # Define all methods in the global namespace to support tab completion
194 for method in methods:
195     paths = method.split(".")
196     if len(paths) > 1:
197         first = paths.pop(0)
198         if first not in globals():
199             globals()[first] = Callable()
200         obj = globals()[first]
201         for path in paths:
202             if not hasattr(obj, path):
203                 if path == paths[-1]:
204                     setattr(obj, path, Callable(method))
205                 else:
206                     setattr(obj, path, Callable())
207             obj = getattr(obj, path)
208     else:
209         globals()[method] = Callable(method)
210
211 pyhelp = help
212 def help(thing):
213     """
214     Override builtin help() function to support calling help(method).
215     """
216
217     # help(method)
218     if isinstance(thing, Callable) and thing.name is not None:
219         pydoc.pager(system.methodHelp(thing.name))
220         return
221
222     # help(help)
223     if thing == help:
224         thing = pyhelp
225
226     # help(...)
227     pyhelp(thing)
228
229 # If a file is specified
230 if argv:
231     execfile(argv[0])
232     sys.exit(0)
233
234 # Otherwise, create an interactive shell environment
235
236 if server is None:
237     print "PlanetLab Central Direct API Access"
238     prompt = ""
239 elif auth['AuthMethod'] == "anonymous":
240     prompt = "[anonymous]"
241     print "Connected anonymously"
242 else:
243     prompt = "[%s %s]" % (auth['Username'], auth['Role'])
244     print "%s connected as %s using %s authentication" % \
245           (auth['Username'], auth['Role'], auth['AuthMethod'])
246 print 'Type "system.listMethods()" or "help(method)" for more information.'
247
248 # Readline and tab completion support
249 import atexit
250 import readline
251 import rlcompleter
252
253 # Load command history
254 history_path = os.path.join(os.environ["HOME"], ".plcapi_history")
255 try:
256     file(history_path, 'a').close()
257     readline.read_history_file(history_path)
258     atexit.register(readline.write_history_file, history_path)
259 except IOError:
260     pass
261
262 # Enable tab completion
263 readline.parse_and_bind("tab: complete")
264
265 try:
266     while True:
267         command = ""
268         while True:
269             # Get line
270             try:
271                 if command == "":
272                     sep = ">>> "
273                 else:
274                     sep = "... "
275                 line = raw_input(prompt + sep)
276             # Ctrl-C
277             except KeyboardInterrupt:
278                 command = ""
279                 print
280                 break
281
282             # Build up multi-line command
283             command += line
284
285             # Blank line or first line does not end in :
286             if line == "" or (command == line and line[-1] != ':'):
287                 break
288
289             command += os.linesep
290
291         # Blank line
292         if command == "":
293             continue
294         # Quit
295         elif command in ["q", "quit", "exit"]:
296             break
297
298         try:
299             try:
300                 # Try evaluating as an expression and printing the result
301                 result = eval(command)
302                 if result is not None:
303                     print result
304             except:
305                 # Fall back to executing as a statement
306                 exec command
307         except Exception, err:
308             traceback.print_exc()
309
310 except EOFError:
311     print
312     pass