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