make read-only a Parameter attribute
[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.3 2006/09/08 00:29:34 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)
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
159             try:
160                 func = api.callable(method)
161                 if func.accepts and \
162                    (isinstance(func.accepts[0], Auth) or \
163                     (isinstance(func.accepts[0], Mixed) and \
164                      filter(lambda param: isinstance(param, Auth), func.accepts[0]))):
165                     self.auth = True
166             except:
167                 # XXX Ignore undefined methods for now
168                 pass
169
170             if server is not None:
171                 self.func = getattr(server, method)
172             else:
173                 self.func = func
174
175     def __call__(self, *args):
176         """
177         Automagically add the authentication structure if the function
178         requires it and it has not been specified.
179         """
180
181         if self.auth and \
182            (not args or not isinstance(args[0], dict) or not args[0].has_key('AuthMethod')):
183             return self.func(auth, *args)
184         else:
185             return self.func(*args)
186
187 if server is not None:
188     methods = server.system.listMethods()
189 else:
190     methods = api.call(None, "system.listMethods")
191
192 # Define all methods in the global namespace to support tab completion
193 for method in methods:
194     paths = method.split(".")
195     if len(paths) > 1:
196         first = paths.pop(0)
197         if first not in globals():
198             globals()[first] = Callable()
199         obj = globals()[first]
200         for path in paths:
201             if not hasattr(obj, path):
202                 if path == paths[-1]:
203                     setattr(obj, path, Callable(method))
204                 else:
205                     setattr(obj, path, Callable())
206             obj = getattr(obj, path)
207     else:
208         globals()[method] = Callable(method)
209
210 pyhelp = help
211 def help(thing):
212     """
213     Override builtin help() function to support calling help(method).
214     """
215
216     # help(method)
217     if isinstance(thing, Callable) and thing.name is not None:
218         pydoc.pager(system.methodHelp(thing.name))
219         return
220
221     # help(help)
222     if thing == help:
223         thing = pyhelp
224
225     # help(...)
226     pyhelp(thing)
227
228 # If a file is specified
229 if argv:
230     execfile(argv[0])
231     sys.exit(0)
232
233 # Otherwise, create an interactive shell environment
234
235 if server is None:
236     print "PlanetLab Central Direct API Access"
237     prompt = ""
238 elif auth['AuthMethod'] == "anonymous":
239     prompt = "[anonymous]"
240     print "Connected anonymously"
241 else:
242     prompt = "[%s %s]" % (auth['Username'], auth['Role'])
243     print "%s connected as %s using %s authentication" % \
244           (auth['Username'], auth['Role'], auth['AuthMethod'])
245 print 'Type "system.listMethods()" or "help(method)" for more information.'
246
247 # Readline and tab completion support
248 import atexit
249 import readline
250 import rlcompleter
251
252 # Load command history
253 history_path = os.path.join(os.environ["HOME"], ".plcapi_history")
254 try:
255     file(history_path, 'a').close()
256     readline.read_history_file(history_path)
257     atexit.register(readline.write_history_file, history_path)
258 except IOError:
259     pass
260
261 # Enable tab completion
262 readline.parse_and_bind("tab: complete")
263
264 try:
265     while True:
266         command = ""
267         while True:
268             # Get line
269             try:
270                 if command == "":
271                     sep = ">>> "
272                 else:
273                     sep = "... "
274                 line = raw_input(prompt + sep)
275             # Ctrl-C
276             except KeyboardInterrupt:
277                 command = ""
278                 print
279                 break
280
281             # Build up multi-line command
282             command += line
283
284             # Blank line or first line does not end in :
285             if line == "" or (command == line and line[-1] != ':'):
286                 break
287
288             command += os.linesep
289
290         # Blank line
291         if command == "":
292             continue
293         # Quit
294         elif command in ["q", "quit", "exit"]:
295             break
296
297         try:
298             try:
299                 # Try evaluating as an expression and printing the result
300                 result = eval(command)
301                 if result is not None:
302                     print result
303             except:
304                 # Fall back to executing as a statement
305                 exec command
306         except Exception, err:
307             traceback.print_exc()
308
309 except EOFError:
310     print
311     pass