Setting tag plcapi-7.0-0
[plcapi.git] / PLC / Shell.py
1 #!/usr/bin/env python3
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
9 import os
10 import pydoc
11 import xmlrpc.client
12
13 from PLC.API import PLCAPI
14 from PLC.Parameter import Mixed
15 from PLC.Auth import Auth
16 from PLC.Config import Config
17 from PLC.Method import Method
18 from PLC.PyCurl import PyCurlTransport
19 import PLC.Methods
20
21 class Callable:
22     """
23     Wrapper to call a method either directly or remotely and
24     automagically add the authentication structure if necessary.
25     """
26
27     def __init__(self, shell, name, func, auth = None):
28         self.shell = shell
29         self.name = name
30         self.func = func
31         self.auth = auth
32
33     def __call__(self, *args, **kwds):
34         """
35         Automagically add the authentication structure if the function
36         requires it and it has not been specified.
37         """
38
39         if self.auth and \
40            (not args or not isinstance(args[0], dict) or \
41             ('AuthMethod' not in args[0] and \
42              'session' not in args[0])):
43             args = (self.auth,) + args
44
45         if self.shell.multi:
46             self.shell.calls.append({'methodName': self.name, 'params': list(args)})
47             return None
48         else:
49             return self.func(*args, **kwds)
50
51 class Shell:
52     def __init__(self,
53                  # Add API functions to global scope
54                  globals = None,
55                  # Configuration file
56                  config = None,
57                  # XML-RPC server
58                  url = None, xmlrpc = False, cacert = None,
59                  # API authentication method
60                  method = None,
61                  # Password authentication
62                  role = None, user = None, password = None,
63                  # Session authentication
64                  session = None):
65         """
66         Initialize a new shell instance. Re-initializes globals.
67         """
68
69         try:
70             # If any XML-RPC options have been specified, do not try
71             # connecting directly to the DB.
72             if (url, method, user, password, role, cacert, xmlrpc) != \
73                    (None, None, None, None, None, None, False):
74                 raise Exception
75
76             # Otherwise, first try connecting directly to the DB. This
77             # absolutely requires a configuration file; the API
78             # instance looks for one in a default location if one is
79             # not specified. If this fails, try connecting to the API
80             # server via XML-RPC.
81             if config is None:
82                 self.api = PLCAPI()
83             else:
84                 self.api = PLCAPI(config)
85             self.config = self.api.config
86             self.url = None
87             self.server = None
88         except Exception as err:
89             # Try connecting to the API server via XML-RPC
90             self.api = PLCAPI(None)
91
92             try:
93                 if config is None:
94                     self.config = Config()
95                 else:
96                     self.config = Config(config)
97             except Exception as err:
98                 # Try to continue if no configuration file is available
99                 self.config = None
100
101             if url is None:
102                 if self.config is None:
103                     raise Exception("Must specify API URL")
104
105                 url = "https://" + self.config.PLC_API_HOST + \
106                       ":" + str(self.config.PLC_API_PORT) + \
107                       "/" + self.config.PLC_API_PATH + "/"
108
109                 if cacert is None:
110                     cacert = self.config.PLC_API_CA_SSL_CRT
111
112             self.url = url
113             if cacert is not None:
114                 self.server = xmlrpc.client.ServerProxy(url, PyCurlTransport(url, cacert), allow_none = 1)
115             else:
116                 self.server = xmlrpc.client.ServerProxy(url, allow_none = 1)
117
118         # Set up authentication structure
119
120         # Default is to use session or capability authentication
121         if (method, user, password) == (None, None, None):
122             if session is not None or os.path.exists("/etc/planetlab/session"):
123                 method = "session"
124                 if session is None:
125                     session = "/etc/planetlab/session"
126             else:
127                 method = "capability"
128
129         if method == "capability":
130             # Load defaults from configuration file if using capability
131             # authentication.
132             if user is None and self.config is not None:
133                 user = self.config.PLC_API_MAINTENANCE_USER
134             if password is None and self.config is not None:
135                 password = self.config.PLC_API_MAINTENANCE_PASSWORD
136             if role is None:
137                 role = "admin"
138         elif method is None:
139             # Otherwise, default to password authentication
140             method = "password"
141
142         if role == "anonymous" or method == "anonymous":
143             self.auth = {'AuthMethod': "anonymous"}
144         elif method == "session":
145             if session is None:
146                 raise Exception("Must specify session")
147
148             if os.path.exists(session):
149                 with open(session) as feed:
150                     session = feed.read()
151
152             self.auth = {'AuthMethod': "session", 'session': session}
153         else:
154             if user is None:
155                 raise Exception("Must specify username")
156
157             if password is None:
158                 raise Exception("Must specify password")
159
160             self.auth = {'AuthMethod': method,
161                          'Username': user,
162                          'AuthString': password}
163
164             if role is not None:
165                 self.auth['Role'] = role
166
167         for method in PLC.API.PLCAPI.all_methods:
168             api_function = self.api.callable(method)
169
170             if self.server is None:
171                 # Can just call it directly
172                 func = api_function
173             else:
174                 func = getattr(self.server, method)
175
176             # If the function requires an authentication structure as
177             # its first argument, automagically add an auth struct to
178             # the call.
179             if api_function.accepts and \
180                (isinstance(api_function.accepts[0], Auth) or \
181                 (isinstance(api_function.accepts[0], Mixed) and \
182                  [param for param in api_function.accepts[0] if isinstance(param, Auth)])):
183                 auth = self.auth
184             else:
185                 auth = None
186
187             callable = Callable(self, method, func, auth)
188
189             # Add to ourself and the global environment. Add dummy
190             # subattributes to support tab completion of methods with
191             # dots in their names (e.g., system.listMethods).
192             class Dummy: pass
193             paths = method.split(".")
194             if len(paths) > 1:
195                 first = paths.pop(0)
196
197                 if not hasattr(self, first):
198                     obj = Dummy()
199                     setattr(self, first, obj)
200                     # Also add to global environment if specified
201                     if globals is not None:
202                         globals[first] = obj
203
204                 obj = getattr(self, first)
205
206                 for path in paths:
207                     if not hasattr(obj, path):
208                         if path == paths[-1]:
209                             setattr(obj, path, callable)
210                         else:
211                             setattr(obj, path, Dummy())
212                     obj = getattr(obj, path)
213             else:
214                 setattr(self, method, callable)
215                 # Also add to global environment if specified
216                 if globals is not None:
217                     globals[method] = callable
218
219         # Override help(), begin(), and commit()
220         if globals is not None:
221             globals['help'] = self.help
222             globals['begin'] = self.begin
223             globals['commit'] = self.commit
224
225         # Multicall support
226         self.calls = []
227         self.multi = False
228
229     def help(self, topic = None):
230         if isinstance(topic, Callable):
231             pydoc.pager(self.system.methodHelp(topic.name))
232         else:
233             pydoc.help(topic)
234
235     def begin(self):
236         if self.calls:
237             raise Exception("multicall already in progress")
238
239         self.multi = True
240
241     def commit(self):
242         if self.calls:
243             ret = []
244             self.multi = False
245             results = self.system.multicall(self.calls)
246             for result in results:
247                 if type(result) == type({}):
248                     raise xmlrpc.client.Fault(result['faultCode'], result['faultString'])
249                 elif type(result) == type([]):
250                     ret.append(result[0])
251                 else:
252                     raise ValueError("unexpected type in multicall result")
253         else:
254             ret = None
255
256         self.calls = []
257         self.multi = False
258
259         return ret