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