- added class variables: event_type, object_type, object_ids (used by event logger)
[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.7 2006/10/19 19:58:50 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 user is None:
124         print "Error: must specify a username with -u"
125         usage()
126
127     if password is None:
128         try:
129             password = getpass.getpass()
130         except (EOFError, KeyboardInterrupt):
131             print
132             sys.exit(0)
133
134     auth = {'AuthMethod': method,
135             'Username': user,
136             'AuthString': password}
137
138     if role is not None:
139         auth['Role'] = role
140
141 class Callable:
142     """
143     Wrapper to call a method either directly or remotely. Initialize
144     with no arguments to use as a dummy class to support tab
145     completion of API methods with dots in their names (e.g.,
146     system.listMethods).
147     """
148
149     def __init__(self, method = None):
150         self.name = method
151
152         if method is not None:
153             # Figure out if the function requires an authentication
154             # structure as its first argument.
155             self.auth = False
156             
157             try:
158                 func = api.callable(method)
159                 if func.accepts and \
160                    (isinstance(func.accepts[0], Auth) or \
161                     (isinstance(func.accepts[0], Mixed) and \
162                      filter(lambda param: isinstance(param, Auth), func.accepts[0]))):
163                     self.auth = True
164             except:
165                 traceback.print_exc()
166                 # XXX Ignore undefined methods for now
167                 pass
168
169             if server is not None:
170                 self.func = getattr(server, method)
171             else:
172                 self.func = func
173
174     def __call__(self, *args, **kwds):
175         """
176         Automagically add the authentication structure if the function
177         requires it and it has not been specified.
178         """
179
180         if self.auth and \
181            (not args or not isinstance(args[0], dict) or not args[0].has_key('AuthMethod')):
182             return self.func(auth, *args, **kwds)
183         else:
184             return self.func(*args, **kwds)
185
186 if server is not None:
187     methods = server.system.listMethods()
188 else:
189     methods = api.call(None, "system.listMethods")
190
191 # Define all methods in the global namespace to support tab completion
192 for method in methods:
193     paths = method.split(".")
194     if len(paths) > 1:
195         first = paths.pop(0)
196         if first not in globals():
197             globals()[first] = Callable()
198         obj = globals()[first]
199         for path in paths:
200             if not hasattr(obj, path):
201                 if path == paths[-1]:
202                     setattr(obj, path, Callable(method))
203                 else:
204                     setattr(obj, path, Callable())
205             obj = getattr(obj, path)
206     else:
207         globals()[method] = Callable(method)
208
209 pyhelp = help
210 def help(thing):
211     """
212     Override builtin help() function to support calling help(method).
213     """
214
215     # help(method)
216     if isinstance(thing, Callable) and thing.name is not None:
217         pydoc.pager(system.methodHelp(thing.name))
218         return
219
220     # help(help)
221     if thing == help:
222         thing = pyhelp
223
224     # help(...)
225     pyhelp(thing)
226
227 # If a file is specified
228 if argv:
229     execfile(argv[0])
230     sys.exit(0)
231
232 # Otherwise, create an interactive shell environment
233
234 if server is None:
235     print "PlanetLab Central Direct API Access"
236     prompt = ""
237 elif auth['AuthMethod'] == "anonymous":
238     prompt = "[anonymous]"
239     print "Connected anonymously"
240 else:
241     prompt = "[%s]" % auth['Username']
242     print "%s connected using %s authentication" % \
243           (auth['Username'], auth['AuthMethod'])
244 print 'Type "system.listMethods()" or "help(method)" for more information.'
245
246 # Readline and tab completion support
247 import atexit
248 import readline
249 import rlcompleter
250
251 # Load command history
252 history_path = os.path.join(os.environ["HOME"], ".plcapi_history")
253 try:
254     file(history_path, 'a').close()
255     readline.read_history_file(history_path)
256     atexit.register(readline.write_history_file, history_path)
257 except IOError:
258     pass
259
260 # Enable tab completion
261 readline.parse_and_bind("tab: complete")
262
263 try:
264     while True:
265         command = ""
266         while True:
267             # Get line
268             try:
269                 if command == "":
270                     sep = ">>> "
271                 else:
272                     sep = "... "
273                 line = raw_input(prompt + sep)
274             # Ctrl-C
275             except KeyboardInterrupt:
276                 command = ""
277                 print
278                 break
279
280             # Build up multi-line command
281             command += line
282
283             # Blank line or first line does not end in :
284             if line == "" or (command == line and line[-1] != ':'):
285                 break
286
287             command += os.linesep
288
289         # Blank line
290         if command == "":
291             continue
292         # Quit
293         elif command in ["q", "quit", "exit"]:
294             break
295
296         try:
297             try:
298                 # Try evaluating as an expression and printing the result
299                 result = eval(command)
300                 if result is not None:
301                     print result
302             except SyntaxError:
303                 # Fall back to executing as a statement
304                 exec command
305         except Exception, err:
306             traceback.print_exc()
307
308 except EOFError:
309     print
310     pass