source: fedd/abac_client.py @ 68bb551

axis_examplecompt_changesinfo-opsversion-2.00version-3.01version-3.02
Last change on this file since 68bb551 was 19a3e06, checked in by Jay Jacobs <Jay.Jacobs@…>, 15 years ago

Initial abac client version

  • Property mode set to 100755
File size: 23.4 KB
Line 
1#!/usr/local/bin/python
2
3import sys
4import os
5import pwd
6import tempfile
7import traceback
8import subprocess
9import re
10import xml.parsers.expat
11import time
12
13from federation import fedid, service_error
14from federation.util import fedd_ssl_context, pack_id, unpack_id
15from federation.abac_remote_service import abac_service_caller
16#from federation import topdl
17
18from optparse import OptionParser, OptionValueError
19
20class IDFormatException(RuntimeError): pass
21
22class access_method:
23    """Encapsulates an access method generically."""
24    (type_ssh, type_x509, type_pgp) = ('sshPubkey', 'X509', 'pgpPubkey')
25    default_type = type_ssh
26    def __init__(self, buf=None, type=None, file=None):
27        self.buf = buf
28
29        if type != None: self.type = type
30        else: self.type = access_method.default_type
31
32        if file != None:
33            self.readfile(file)
34   
35    def readfile(self, file, type=None):
36        f = open(file, "r")
37        self.buf = f.read();
38        f.close()
39        if type == None:
40            if self.type == None:
41                self.type = access_method.default_type
42        else:
43            self.type = type;
44
45
46# Base class that will do a the SOAP/XMLRPC exchange for a request.
47class abac_rpc:
48    class RPCException:
49        def __init__(self, fb):
50            self.desc = fb.get('desc', None)
51            self.code = fb.get('code', -1)
52            self.errstr = fb.get('errstr', None)
53
54    def __init__(self, pre):
55        """
56        Specialize the class for the pre method
57        """
58        self.RequestBody="%sRequestBody" % pre
59        self.ResponseBody="%sResponseBody" % pre
60        self.method = pre
61
62        self.caller = abac_service_caller(self.method)
63        self.RPCException = abac_rpc.RPCException
64
65
66    def add_ssh_key(self, option, opt_str, value, parser, access_keys):
67        try:
68            access_keys.append(access_method(file=value,
69                type=access_method.type_ssh))
70        except IOError, (errno, strerror):
71            raise OptionValueError("Cannot generate sshPubkey from %s: "\
72                    "%s (%d)" % (value,strerror,errno))
73
74    def add_x509_cert(self, option, opt_str, value, parser, access_keys):
75        try:
76            access_keys.append(access_method(file=value,
77                type=access_method.type_x509))
78        except IOError, (errno, strerror):
79            raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" %
80                    (value,strerror,errno))
81
82    def add_node_desc(self, option, opt_str, value, parser, node_descs):
83        def none_if_zero(x):
84            if len(x) > 0: return x
85            else: return None
86
87        params = map(none_if_zero, value.split(":"));
88
89        if len(params) < 4 and len(params) > 1:
90            node_descs.append(node_desc(*params))
91        else:
92            raise OptionValueError("Bad node description: %s" % value)
93
94    def get_user_info(self, access_keys):
95        pw = pwd.getpwuid(os.getuid());
96        try_cert=None
97        user = None
98
99        if pw != None:
100            user = pw[0]
101            try_cert = "%s/.ssl/emulab.pem" % pw[5];
102            if not os.access(try_cert, os.R_OK):
103                try_cert = None
104            if len(access_keys) == 0:
105                for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub",
106                        "%s/.ssh/identity.pub"]:
107                    try_key = k % pw[5];
108                    if os.access(try_key, os.R_OK):
109                        access_keys.append(access_method(file=try_key,
110                            type=access_method.type_ssh))
111                        break
112        return (user, try_cert)
113
114    def do_rpc(self, req_dict, url, transport, cert, trusted, tracefile=None,
115            serialize_only=False):
116        """
117        The work of sending and parsing the RPC as either XMLRPC or SOAP
118        """
119
120        context = None
121        while context == None:
122            try:
123                context = fedd_ssl_context(cert, trusted)
124            except Exception, e:
125                # Yes, doing this on message type is not ideal.  The string
126                # comes from OpenSSL, so check there is this stops working.
127                if str(e) == "bad decrypt":
128                    print >>sys.stderr, "Bad Passphrase given."
129                else: raise
130
131        if transport == "soap":
132            if serialize_only:
133                print self.caller.serialize_soap(req_dict)
134                sys.exit(0)
135            else:
136                try:
137                    resp = self.caller.call_soap_service(url, req_dict,
138                            context=context, tracefile=tracefile)
139                except service_error, e:
140                    raise self.RPCException( {\
141                            'code': e.code,
142                            'desc': e.desc,
143                            'errstr': e.code_string()\
144                        })
145        elif transport == "xmlrpc":
146            if serialize_only:
147                ser = dumps((req_dict,))
148                print ser
149                sys.exit(0)
150            else:
151                try:
152                    resp = self.caller.call_xmlrpc_service(url, req_dict,
153                            context=context, tracefile=tracefile)
154                except service_error, e:
155                    raise self.RPCException( {\
156                            'code': e.code,
157                            'desc': e.desc,
158                            'errstr': e.code_string()\
159                        })
160
161        else:
162            raise RuntimeError("Unknown RPC transport: %s" % transport)
163
164        if resp.has_key(self.ResponseBody):
165            return resp[self.ResponseBody]
166        else:
167            raise  RuntimeError("No body in response??")
168
169class abac_client_opts(OptionParser):
170    """Encapsulate option processing in this class, rather than in main"""
171    def __init__(self):
172        OptionParser.__init__(self, usage="%prog [opts] (--help for details)",
173                version="0.1")
174
175        self.add_option("-c","--cert", action="store", dest="cert",
176                type="string", help="my certificate file")
177        self.add_option("-d", "--debug", action="count", dest="debug", 
178                default=0, help="Set debug.  Repeat for more information")
179        self.add_option("-s", "--serializeOnly", action="store_true", 
180                dest="serialize_only", default=False,
181                help="Print the SOAP request that would be sent and exit")
182        self.add_option("-T","--trusted", action="store", dest="trusted",
183                type="string", help="Trusted certificates (required)")
184        self.add_option("-u", "--url", action="store", dest="url",
185                type="string",default="https://localhost:23235", 
186                help="URL to connect to (default %default)")
187        self.add_option("-F","--useFedid", action="store_true",
188                dest="use_fedid", default=False,
189                help="Use a fedid derived from my certificate as user identity")
190        self.add_option("-x","--transport", action="store", type="choice",
191                choices=("xmlrpc", "soap"), default="soap",
192                help="Transport for request (xmlrpc|soap) (Default: %default)")
193        self.add_option("--trace", action="store_const", dest="tracefile", 
194                const=sys.stderr, help="Print SOAP exchange to stderr")
195
196class abac_create_opts(abac_client_opts):
197    def __init__(self, access_keys, add_key_callback=None, 
198            add_cert_callback=None):
199        abac_client_opts.__init__(self)
200        self.add_option("-f", "--file", dest="file", type="string",
201                help="negotiation context remote path name")
202        if add_key_callback:
203            self.add_option("-k", "--ssh_key", action="callback",
204                    type="string", callback=add_key_callback,
205                    callback_args=(access_keys,),
206                    help="ssh key for access (can be supplied more than once")
207        if add_cert_callback:
208            self.add_option("-K", "--x509Key", action="callback",
209                    type="string", callback=add_cert_callback,
210                    callback_args=(access_keys,),
211                    help="X509 certificate for access " + \
212                        "(can be supplied more than once")
213            self.add_option("-m", "--master", dest="master",
214                    help="Master testbed in the federation")
215            self.add_option("-U", "--username", action="store", dest="user",
216                    type="string", 
217                    help="Use this username instead of the uid")
218
219class abac_access_opts(abac_create_opts):
220    def __init__(self, access_keys, node_descs, add_key_callback=None, 
221            add_cert_callback=None, add_node_callback=None):
222        abac_create_opts.__init__(self, access_keys, add_key_callback,
223                add_cert_callback)
224        self.add_option("-a","--anonymous", action="store_true",
225                dest="anonymous", default=False,
226                help="Do not include a user in the request")
227        self.add_option("-l","--label", action="store", dest="label",
228                type="string", help="Label for output")
229        if add_node_callback:
230            self.add_option("-n", "--node", action="callback", type="string", 
231                    callback=add_node_callback, callback_args=(node_descs,),
232                    help="Node description: image:hardware[:count]")
233        self.add_option("-t", "--testbed", action="store", dest="testbed",
234                type="string",
235                help="Testbed identifier (URI) to contact (required)")
236
237
238class abac_negotiate_opts(abac_create_opts):
239    def __init__(self, access_keys, node_descs, add_key_callback=None, 
240            add_cert_callback=None, add_node_callback=None):
241        abac_create_opts.__init__(self, access_keys, add_key_callback,
242                add_cert_callback)
243        self.add_option("-a","--anonymous", action="store_true",
244                dest="anonymous", default=False,
245                help="Do not include a user in the request")
246        self.add_option("-l","--label", action="store", dest="label",
247                type="string", help="Label for output")
248        if add_node_callback:
249            self.add_option("-n", "--node", action="callback", type="string", 
250                    callback=add_node_callback, callback_args=(node_descs,),
251                    help="Node description: image:hardware[:count]")
252        self.add_option("-t", "--testbed", action="store", dest="testbed",
253                type="string",
254                help="Testbed identifier (URI) to contact (required)")
255
256
257
258
259class exp_data_base(abac_rpc):
260    def __init__(self, op='Info'):
261        """
262        Init the various conversions
263        """
264
265        abac_rpc.__init__(self, op)
266        # List of things one could ask for and what formatting routine is
267        # called.
268        self.params = {
269                'vis': ('vis', self.print_xml('vis')),
270                'vtopo': ('vtopo', self.print_xml('vtopo')),
271                'federant': ('federant', self.print_xml),
272                'id': ('experimentID', self.print_id),
273                'status': ('experimentStatus', self.print_string),
274                'log': ('allocationLog', self.print_string),
275                'access': ('experimentAccess', self.print_string),
276            }
277
278    # Utility functions
279    def print_string(self, d, out=sys.stdout):
280        print >>out, d
281
282    def print_id(self, d, out=sys.stdout):
283        if d:
284            for id in d:
285                for k in id.keys():
286                    print >>out, "%s: %s" % (k, id[k])
287
288    class print_xml:
289        """
290        Print the retrieved data is a simple xml representation of the dict.
291        """
292        def __init__(self, top):
293            self.xml = top
294
295        def __call__(self, d, out=sys.stdout):
296            str = "<%s>\n" % self.xml
297            for t in ('node', 'lan'):
298                if d.has_key(t):
299                    for x in d[t]:
300                        str += "<%s>" % t
301                        for k in x.keys():
302                            str += "<%s>%s</%s>" % (k, x[k],k)
303                        str += "</%s>\n" % t
304            str+= "</%s>" % self.xml
305            print >>out, str
306
307
308
309class context_data(exp_data_base):
310    def __init__(self):
311        exp_data_base.__init__(self, 'MultiInfo')
312
313
314    def __call__(self):
315        """
316        The control flow.  Compose the request and print the response.
317        """
318        # Process the options using the customized option parser defined above
319        parser = abac_multi_exp_data_opts()
320
321        (opts, args) = parser.parse_args()
322
323        if opts.trusted:
324            if ( not os.access(opts.trusted, os.R_OK) ) :
325                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
326
327        if opts.debug > 0: opts.tracefile=sys.stderr
328
329        (user, cert) = self.get_user_info([])
330
331        if opts.cert != None: cert = opts.cert
332
333        if cert == None:
334            sys.exit("No certificate given (--cert) or found")
335
336        if opts.file == None:
337            sys.exit("No file given (--file)")
338
339        if os.access(cert, os.R_OK):
340            fid = fedid(file=cert)
341        else:
342            sys.exit("Cannot read certificate (%s)" % cert)
343
344        req = { 'ContextIn': { 'contextFile': opts.file } }
345
346        try:
347            resp_dict = self.do_rpc(req,
348                    opts.url, opts.transport, cert, opts.trusted,
349                    serialize_only=opts.serialize_only,
350                    tracefile=opts.tracefile)
351        except self.RPCException, e:
352            exit_with_fault(\
353                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
354        except RuntimeError, e:
355            sys.exit("Error processing RPC: %s" % e)
356
357        exps = resp_dict.get('info', [])
358        if exps:
359            print '---'
360            for exp in exps:
361                for d in opts.data:
362                    key, output = self.params[d]
363                    try:
364                        if exp.has_key(key):
365                            output(exp[key])
366                    except RuntimeError, e:
367                        sys.exit("Bad response. %s" % e.message)
368                print '---'
369
370
371    def __call__(self):
372        """
373        The control flow.  Compose the request and print the response.
374        """
375        # Process the options using the customized option parser defined above
376        parser = fedd_image_opts()
377
378        (opts, args) = parser.parse_args()
379
380        if opts.trusted:
381            if ( not os.access(opts.trusted, os.R_OK) ) :
382                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
383
384        if opts.debug > 0: opts.tracefile=sys.stderr
385
386        (user, cert) = self.get_user_info([])
387
388        if opts.cert != None: cert = opts.cert
389
390        if cert == None:
391            sys.exit("No certificate given (--cert) or found")
392
393        if os.access(cert, os.R_OK):
394            fid = fedid(file=cert)
395        else:
396            sys.exit("Cannot read certificate (%s)" % cert)
397
398        if opts.exp_name and opts.exp_certfile:
399            sys.exit("Only one of --experiment_cert and " +\
400                    "--experiment_name permitted");
401
402        if opts.exp_certfile:
403            exp_id = { 'fedid': fedid(file=opts.exp_certfile) }
404
405        if opts.exp_name:
406            exp_id = { 'localname' : opts.exp_name }
407
408        if opts.format and opts.outfile:
409            fmt = opts.format
410            file = opts.outfile
411        elif not opts.format and opts.outfile:
412            fmt = opts.outfile[-3:]
413            if fmt not in ("png", "jpg", "dot", "svg"):
414                sys.exit("Unexpected file type and no format specified")
415            file = opts.outfile
416        elif opts.format and not opts.outfile:
417            fmt = opts.format
418            file = None
419        else:
420            fmt="dot"
421            file = None
422
423
424        req = { 'experiment': exp_id }
425
426        try:
427            resp_dict = self.do_rpc(req,
428                    opts.url, opts.transport, cert, opts.trusted,
429                    serialize_only=opts.serialize_only,
430                    tracefile=opts.tracefile)
431        except self.RPCException, e:
432            exit_with_fault(\
433                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
434        except RuntimeError, e:
435            sys.exit("Error processing RPC: %s" % e)
436
437
438        if resp_dict.has_key('vtopo'):
439            self.gen_image(resp_dict['vtopo'],
440                    len(resp_dict['vtopo'].get('node', [])),
441                    file, fmt, opts.neato, opts.labels, opts.pixels)
442        else:
443            sys.exit("Bad response. %s" % e.message)
444
445
446class create(abac_rpc):
447    def __init__(self): 
448        abac_rpc.__init__(self, "CreateContext")
449    def __call__(self):
450        access_keys = []
451        # Process the options using the customized option parser defined above
452        parser = abac_create_opts(access_keys, self.add_ssh_key,
453                self.add_x509_cert)
454
455        (opts, args) = parser.parse_args()
456
457        if opts.trusted:
458            if ( not os.access(opts.trusted, os.R_OK) ) :
459                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
460
461        if opts.debug > 0: opts.tracefile=sys.stderr
462
463        (user, cert) = self.get_user_info(access_keys)
464
465        if opts.user: user = opts.user
466
467        if opts.cert != None: cert = opts.cert
468
469        if cert == None:
470            sys.exit("No certificate given (--cert) or found")
471
472        if os.access(cert, os.R_OK):
473            fid = fedid(file=cert)
474            if opts.use_fedid == True:
475                user = fid
476        else:
477            sys.exit("Cannot read certificate (%s)" % cert)
478
479#       if opts.file:
480#           exp_desc = ""
481#           try:
482#               f = open(opts.file, 'r')
483#               for line in f:
484#                   exp_desc += line
485#               f.close()
486#           except IOError:
487#               sys.exit("Cannot read description file (%s)" %opts.file)
488#       else:
489#           sys.exit("Must specify an experiment description (--file)")
490#
491#       if not opts.master:
492#           sys.exit("Must specify a master testbed (--master)")
493#
494#       out_certfile = opts.out_certfile
495
496        print "file = %s" % opts.file
497#       msg = { 'ContextIn': { 'contextFile': opts.file } }
498        msg = { 'contextFile': opts.file }
499
500        if opts.debug > 1: print >>sys.stderr, msg
501
502        try:
503            resp_dict = self.do_rpc(msg, 
504                    opts.url, opts.transport, cert, opts.trusted, 
505                    serialize_only=opts.serialize_only,
506                    tracefile=opts.tracefile)
507        except self.RPCException, e:
508            exit_with_fault(\
509                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
510        except RuntimeError, e:
511            sys.exit("Error processing RPC: %s" % e)
512
513        if opts.debug > 1: print >>sys.stderr, resp_dict
514
515        ea = resp_dict.get('experimentAccess', None)
516        if out_certfile and ea and ea.has_key('X509'):
517            try:
518                f = open(out_certfile, "w")
519                print >>f, ea['X509']
520                f.close()
521            except IOError:
522                sys.exit('Could not write to %s' %  out_certfile)
523        eid = resp_dict.get('experimentID', None)
524        if eid:
525            for id in eid:
526                for k in id.keys():
527                    print "%s: %s" % (k, id[k])
528        st = resp_dict.get('experimentStatus', None)
529        if st:
530            print "status: %s" % st
531
532class negotiate(abac_rpc):
533    def __init__(self): 
534        abac_rpc.__init__(self, "Negotiate")
535    def __call__(self):
536        access_keys = []
537        # Process the options using the customized option parser defined above
538        parser = abac_create_opts(access_keys, self.add_ssh_key,
539                self.add_x509_cert)
540
541        (opts, args) = parser.parse_args()
542
543        if opts.trusted:
544            if ( not os.access(opts.trusted, os.R_OK) ) :
545                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
546
547        if not opts.project :
548            parser.error('--project is required')
549
550        if opts.debug > 0: opts.tracefile=sys.stderr
551
552        (user, cert) = self.get_user_info(access_keys)
553
554        if opts.user: user = opts.user
555
556        if opts.cert != None: cert = opts.cert
557
558        if cert == None:
559            sys.exit("No certificate given (--cert) or found")
560
561        if os.access(cert, os.R_OK):
562            fid = fedid(file=cert)
563            if opts.use_fedid == True:
564                user = fid
565        else:
566            sys.exit("Cannot read certificate (%s)" % cert)
567
568        if opts.file:
569            exp_desc = ""
570            try:
571                f = open(opts.file, 'r')
572                for line in f:
573                    exp_desc += line
574                f.close()
575            except IOError:
576                sys.exit("Cannot read description file (%s)" %opts.file)
577        else:
578            sys.exit("Must specify an experiment description (--file)")
579
580        if not opts.master:
581            sys.exit("Must specify a master testbed (--master)")
582
583        out_certfile = opts.out_certfile
584
585        msg = {
586                'experimentdescription': { 'ns2description': exp_desc },
587                'master': opts.master,
588                'exportProject': { 'localname': opts.project },
589                'user' : [ {\
590                        'userID': pack_id(user), \
591                        'access': [ { a.type: a.buf } for a in access_keys]\
592                        } ]
593                }
594
595        if opts.exp_name:
596            msg['experimentID'] = { 'localname': opts.exp_name }
597
598        if opts.debug > 1: print >>sys.stderr, msg
599
600        try:
601            resp_dict = self.do_rpc(msg, 
602                    opts.url, opts.transport, cert, opts.trusted, 
603                    serialize_only=opts.serialize_only,
604                    tracefile=opts.tracefile)
605        except self.RPCException, e:
606            exit_with_fault(\
607                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
608        except RuntimeError, e:
609            sys.exit("Error processing RPC: %s" % e)
610
611        if opts.debug > 1: print >>sys.stderr, resp_dict
612
613        ea = resp_dict.get('experimentAccess', None)
614        if out_certfile and ea and ea.has_key('X509'):
615            try:
616                f = open(out_certfile, "w")
617                print >>f, ea['X509']
618                f.close()
619            except IOError:
620                sys.exit('Could not write to %s' %  out_certfile)
621        eid = resp_dict.get('experimentID', None)
622        if eid:
623            for id in eid:
624                for k in id.keys():
625                    print "%s: %s" % (k, id[k])
626        st = resp_dict.get('experimentStatus', None)
627        if st:
628            print "status: %s" % st
629
630class access(abac_rpc):
631    def __init__(self): 
632        abac_rpc.__init__(self, "Access")
633    def __call__(self):
634        access_keys = []
635        # Process the options using the customized option parser defined above
636        parser = abac_create_opts(access_keys, self.add_ssh_key,
637                self.add_x509_cert)
638
639        (opts, args) = parser.parse_args()
640
641        if opts.trusted:
642            if ( not os.access(opts.trusted, os.R_OK) ) :
643                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
644
645        if not opts.project :
646            parser.error('--project is required')
647
648        if opts.debug > 0: opts.tracefile=sys.stderr
649
650        (user, cert) = self.get_user_info(access_keys)
651
652        if opts.user: user = opts.user
653
654        if opts.cert != None: cert = opts.cert
655
656        if cert == None:
657            sys.exit("No certificate given (--cert) or found")
658
659        if os.access(cert, os.R_OK):
660            fid = fedid(file=cert)
661            if opts.use_fedid == True:
662                user = fid
663        else:
664            sys.exit("Cannot read certificate (%s)" % cert)
665
666        if not opts.id:
667            sys.exit("Must specify a negotiator id (--id)")
668
669        if not opts.goal:
670            sys.exit("Must specify a goal (--file)")
671
672        out_certfile = opts.out_certfile
673
674        msg = {
675                'context': { 'contextID': opts.contextID },
676                'goal': opts.goal
677                }
678
679        if opts.exp_name:
680            msg['experimentID'] = { 'localname': opts.exp_name }
681
682        if opts.debug > 1: print >>sys.stderr, msg
683
684        try:
685            resp_dict = self.do_rpc(msg, 
686                    opts.url, opts.transport, cert, opts.trusted, 
687                    serialize_only=opts.serialize_only,
688                    tracefile=opts.tracefile)
689        except self.RPCException, e:
690            exit_with_fault(\
691                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
692        except RuntimeError, e:
693            sys.exit("Error processing RPC: %s" % e)
694
695        if opts.debug > 1: print >>sys.stderr, resp_dict
696
697        ea = resp_dict.get('experimentAccess', None)
698        if out_certfile and ea and ea.has_key('X509'):
699            try:
700                f = open(out_certfile, "w")
701                print >>f, ea['X509']
702                f.close()
703            except IOError:
704                sys.exit('Could not write to %s' %  out_certfile)
705        eid = resp_dict.get('experimentID', None)
706        if eid:
707            for id in eid:
708                for k in id.keys():
709                    print "%s: %s" % (k, id[k])
710        st = resp_dict.get('experimentStatus', None)
711        if st:
712            print "status: %s" % st
713
714def exit_with_fault(dict, out=sys.stderr):
715    """ Print an error message and exit.
716
717    The dictionary contains the FeddFaultBody elements."""
718    codestr = ""
719
720    if dict.has_key('errstr'):
721        codestr = "Error: %s" % dict['errstr']
722
723    if dict.has_key('code'):
724        if len(codestr) > 0 : 
725            codestr += " (%d)" % dict['code']
726        else:
727            codestr = "Error Code: %d" % dict['code']
728    print>>out, codestr
729    print>>out, "Description: %s" % dict['desc']
730    traceback.print_stack()
731    sys.exit(dict.get('code', 20))
732
733
734cmds = {\
735        'create': create(),\
736        'access': access()\
737#        'negotiate': negotiate(),\
738#        'status': status()\
739    }
740
741operation = cmds.get(sys.argv[1], None)
742if operation:
743    del sys.argv[1]
744    operation()
745else:
746    if sys.argv[1] == '--help':
747        sys.exit(\
748'''Only context sensitive help is available.  For one of the commands:
749
750%s
751
752type
753  %s command --help
754
755to get help, e.g., %s create --help
756''' % (", ".join(cmds.keys()), sys.argv[0], sys.argv[0]))
757    else:
758        sys.exit("Bad command: %s.  Valid ones are: %s" % \
759                (sys.argv[1], ", ".join(cmds.keys())))
760
Note: See TracBrowser for help on using the repository browser.