extensions in client lib and command line client
[pithos] / tools / store
1 #!/usr/bin/env python
2
3 # Copyright 2011 GRNET S.A. All rights reserved.
4
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
7 # conditions are met:
8
9 #   1. Redistributions of source code must retain the above
10 #      copyright notice, this list of conditions and the following
11 #      disclaimer.
12
13 #   2. Redistributions in binary form must reproduce the above
14 #      copyright notice, this list of conditions and the following
15 #      disclaimer in the documentation and/or other materials
16 #      provided with the distribution.
17
18 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
30
31 # The views and conclusions contained in the software and
32 # documentation are those of the authors and should not be
33 # interpreted as representing official policies, either expressed
34 # or implied, of GRNET S.A.
35
36 from getpass import getuser
37 from optparse import OptionParser
38 from os import environ
39 from sys import argv, exit, stdin, stdout
40 from pithos.lib.client import Client, Fault
41 from datetime import datetime
42
43 import json
44 import logging
45 import types
46 import re
47 import time as _time
48 import os
49
50 #DEFAULT_HOST = 'pithos.dev.grnet.gr'
51 DEFAULT_HOST = '127.0.0.1:8000'
52 DEFAULT_API = 'v1'
53
54 _cli_commands = {}
55
56 def cli_command(*args):
57     def decorator(cls):
58         cls.commands = args
59         for name in args:
60             _cli_commands[name] = cls
61         return cls
62     return decorator
63
64 def class_for_cli_command(name):
65     return _cli_commands[name]
66
67 class Command(object):
68     syntax = ''
69     
70     def __init__(self, name, argv):
71         parser = OptionParser('%%prog %s [options] %s' % (name, self.syntax))
72         parser.add_option('--host', dest='host', metavar='HOST',
73                           default=DEFAULT_HOST, help='use server HOST')
74         parser.add_option('--user', dest='user', metavar='USERNAME',
75                           default=_get_user(),
76                           help='use account USERNAME')
77         parser.add_option('--token', dest='token', metavar='AUTH',
78                           default=_get_auth(),
79                           help='use account AUTH')
80         parser.add_option('--api', dest='api', metavar='API',
81                           default=DEFAULT_API, help='use api API')
82         parser.add_option('-v', action='store_true', dest='verbose',
83                           default=False, help='use verbose output')
84         parser.add_option('-d', action='store_true', dest='debug',
85                           default=False, help='use debug output')
86         self.add_options(parser)
87         options, args = parser.parse_args(argv)
88         
89         # Add options to self
90         for opt in parser.option_list:
91             key = opt.dest
92             if key:
93                 val = getattr(options, key)
94                 setattr(self, key, val)
95         
96         self.client = Client(self.host, self.token, self.user, self.api, self.verbose,
97                              self.debug)
98         
99         self.parser = parser
100         self.args = args
101         
102     def add_options(self, parser):
103         pass
104     
105     def execute(self, *args):
106         pass
107
108 @cli_command('list', 'ls')
109 class List(Command):
110     syntax = '[<container>[/<object>]]'
111     description = 'list containers or objects'
112     
113     def add_options(self, parser):
114         parser.add_option('-l', action='store_true', dest='detail',
115                           default=False, help='show detailed output')
116         parser.add_option('-n', action='store', type='int', dest='limit',
117                           default=1000, help='show limited output')
118         parser.add_option('--marker', action='store', type='str',
119                           dest='marker', default=None,
120                           help='show output greater then marker')
121         parser.add_option('--prefix', action='store', type='str',
122                           dest='prefix', default=None,
123                           help='show output starting with prefix')
124         parser.add_option('--delimiter', action='store', type='str',
125                           dest='delimiter', default=None,
126                           help='show output up to the delimiter')
127         parser.add_option('--path', action='store', type='str',
128                           dest='path', default=None,
129                           help='show output starting with prefix up to /')
130         parser.add_option('--meta', action='store', type='str',
131                           dest='meta', default=None,
132                           help='show output having the specified meta keys')
133         parser.add_option('--if-modified-since', action='store', type='str',
134                           dest='if_modified_since', default=None,
135                           help='show output if modified since then')
136         parser.add_option('--if-unmodified-since', action='store', type='str',
137                           dest='if_unmodified_since', default=None,
138                           help='show output if not modified since then')
139         parser.add_option('--until', action='store', dest='until',
140                           default=False, help='show metadata until that date')
141         parser.add_option('--format', action='store', dest='format',
142                           default='%d/%m/%Y', help='format to parse until date')
143     
144     def execute(self, container=None):
145         if container:
146             self.list_objects(container)
147         else:
148             self.list_containers()
149     
150     def list_containers(self):
151         params = {'limit':self.limit, 'marker':self.marker}
152         headers = {'IF_MODIFIED_SINCE':self.if_modified_since,
153                    'IF_UNMODIFIED_SINCE':self.if_unmodified_since}
154         
155         if self.until:
156             t = _time.strptime(self.until, self.format)
157             params['until'] = int(_time.mktime(t))
158         
159         l = self.client.list_containers(self.detail, params, headers)
160         print_list(l)
161     
162     def list_objects(self, container):
163         #prepate params
164         params = {}
165         attrs = ['limit', 'marker', 'prefix', 'delimiter', 'path', 'meta']
166         for a in [a for a in attrs if getattr(self, a)]:
167             params[a] = getattr(self, a)
168         
169         if self.until:
170             t = _time.strptime(self.until, self.format)
171             params['until'] = int(_time.mktime(t))
172         
173         headers = {'IF_MODIFIED_SINCE':self.if_modified_since,
174                    'IF_UNMODIFIED_SINCE':self.if_unmodified_since}
175         container, sep, object = container.partition('/')
176         if object:
177             return
178         
179         detail = 'json'
180         #if request with meta quering disable trash filtering
181         show_trashed = True if self.meta else False
182         l = self.client.list_objects(container, detail, headers,
183                                      include_trashed = show_trashed, **params)
184         print_list(l, detail=self.detail)
185
186 @cli_command('meta')
187 class Meta(Command):
188     syntax = '[<container>[/<object>]]'
189     description = 'get the metadata of an account, a container or an object'
190     
191     def add_options(self, parser):
192         parser.add_option('-r', action='store_true', dest='restricted',
193                           default=False, help='show only user defined metadata')
194         parser.add_option('--until', action='store', dest='until',
195                           default=False, help='show metadata until that date')
196         parser.add_option('--format', action='store', dest='format',
197                           default='%d/%m/%Y', help='format to parse until date')
198         parser.add_option('--version', action='store', dest='version',
199                           default=None, help='show specific version \
200                                   (applies only for objects)')
201     
202     def execute(self, path=''):
203         container, sep, object = path.partition('/')
204         if self.until:
205             t = _time.strptime(self.until, self.format)
206             self.until = int(_time.mktime(t))
207         if object:
208             meta = self.client.retrieve_object_metadata(container, object,
209                                                         self.restricted,
210                                                         self.version)
211         elif container:
212             meta = self.client.retrieve_container_metadata(container,
213                                                            self.restricted,
214                                                            self.until)
215         else:
216             meta = self.client.account_metadata(self.restricted, self.until)
217         if meta == None:
218             print 'Entity does not exist'
219         else:
220             print_dict(meta, header=None)
221
222 @cli_command('create')
223 class CreateContainer(Command):
224     syntax = '<container> [key=val] [...]'
225     description = 'create a container'
226     
227     def execute(self, container, *args):
228         headers = {}
229         meta = {}
230         for arg in args:
231             key, sep, val = arg.partition('=')
232             meta[key] = val
233         ret = self.client.create_container(container, headers, **meta)
234         if not ret:
235             print 'Container already exists'
236
237 @cli_command('delete', 'rm')
238 class Delete(Command):
239     syntax = '<container>[/<object>]'
240     description = 'delete a container or an object'
241     
242     def execute(self, path):
243         container, sep, object = path.partition('/')
244         if object:
245             self.client.delete_object(container, object)
246         else:
247             self.client.delete_container(container)
248
249 @cli_command('get')
250 class GetObject(Command):
251     syntax = '<container>/<object>'
252     description = 'get the data of an object'
253     
254     def add_options(self, parser):
255         parser.add_option('-l', action='store_true', dest='detail',
256                           default=False, help='show detailed output')
257         parser.add_option('--range', action='store', dest='range',
258                           default=None, help='show range of data')
259         parser.add_option('--if-range', action='store', dest='if-range',
260                           default=None, help='show range of data')
261         parser.add_option('--if-match', action='store', dest='if-match',
262                           default=None, help='show output if ETags match')
263         parser.add_option('--if-none-match', action='store',
264                           dest='if-none-match', default=None,
265                           help='show output if ETags don\'t match')
266         parser.add_option('--if-modified-since', action='store', type='str',
267                           dest='if-modified-since', default=None,
268                           help='show output if modified since then')
269         parser.add_option('--if-unmodified-since', action='store', type='str',
270                           dest='if-unmodified-since', default=None,
271                           help='show output if not modified since then')
272         parser.add_option('-o', action='store', type='str',
273                           dest='file', default=None,
274                           help='save output in file')
275         parser.add_option('--version', action='store', type='str',
276                           dest='version', default=None,
277                           help='get the specific \
278                                version')
279         parser.add_option('--versionlist', action='store_true',
280                           dest='versionlist', default=False,
281                           help='get the full object version list')
282     
283     def execute(self, path):
284         headers = {}
285         if self.range:
286             headers['RANGE'] = 'bytes=%s' %self.range
287         if getattr(self, 'if-range'):
288             headers['IF_RANGE'] = 'If-Range:%s' % getattr(self, 'if-range')
289         attrs = ['if-match', 'if-none-match', 'if-modified-since',
290                  'if-unmodified-since']
291         attrs = [a for a in attrs if getattr(self, a)]
292         for a in attrs:
293             headers[a.replace('-', '_').upper()] = getattr(self, a)
294         container, sep, object = path.partition('/')
295         if self.versionlist:
296             self.version = 'list'
297             self.detail = True
298         data = self.client.retrieve_object(container, object, self.detail,
299                                           headers, self.version)
300         f = self.file and open(self.file, 'w') or stdout
301         if self.detail:
302             data = json.loads(data)
303             if self.versionlist:
304                 print_versions(data, f=f)
305             else:
306                 print_dict(data, f=f)
307         else:
308             f.write(data)
309         f.close()
310
311 @cli_command('mkdir')
312 class PutMarker(Command):
313     syntax = '<container>/<directory marker>'
314     description = 'create a directory marker'
315     
316     def execute(self, path):
317         container, sep, object = path.partition('/')
318         self.client.create_directory_marker(container, object)
319
320 @cli_command('put')
321 class PutObject(Command):
322     syntax = '<container>/<object> <path> [key=val] [...]'
323     description = 'create/override object'
324     
325     def add_options(self, parser):
326         parser.add_option('--use_hashes', action='store_true', dest='use_hashes',
327                           default=False, help='provide hashmap instead of data')
328         parser.add_option('--chunked', action='store_true', dest='chunked',
329                           default=False, help='set chunked transfer mode')
330         parser.add_option('--etag', action='store', dest='etag',
331                           default=None, help='check written data')
332         parser.add_option('--content-encoding', action='store',
333                           dest='content-encoding', default=None,
334                           help='provide the object MIME content type')
335         parser.add_option('--content-disposition', action='store', type='str',
336                           dest='content-disposition', default=None,
337                           help='provide the presentation style of the object')
338         parser.add_option('-S', action='store',
339                           dest='segment-size', default=False,
340                           help='use for large file support')
341         parser.add_option('--manifest', action='store_true',
342                           dest='manifest', default=None,
343                           help='upload a manifestation file')
344         parser.add_option('--type', action='store',
345                           dest='content-type', default=False,
346                           help='create object with specific content type')
347         parser.add_option('--sharing', action='store',
348                           dest='sharing', default=None,
349                           help='define sharing object policy')
350         parser.add_option('-f', action='store',
351                           dest='srcpath', default=None,
352                           help='file descriptor to read from (pass - for standard input)')
353         parser.add_option('--public', action='store',
354                           dest='public', default=None,
355                           help='make object publicly accessible (\'True\'/\'False\')')
356     
357     def execute(self, path, *args):
358         if path.find('=') != -1:
359             raise Fault('Missing path argument')
360         
361         #prepare user defined meta
362         meta = {}
363         for arg in args:
364             key, sep, val = arg.partition('=')
365             meta[key] = val
366         
367         headers = {}
368         manifest = getattr(self, 'manifest')
369         if manifest:
370             # if it's manifestation file
371             # send zero-byte data with X-Object-Manifest header
372             self.touch = True
373             headers['X_OBJECT_MANIFEST'] = manifest
374         if self.sharing:
375             headers['X_OBJECT_SHARING'] = self.sharing
376         
377         attrs = ['etag', 'content-encoding', 'content-disposition',
378                  'content-type']
379         attrs = [a for a in attrs if getattr(self, a)]
380         for a in attrs:
381             headers[a.replace('-', '_').upper()] = getattr(self, a)
382         
383         container, sep, object = path.partition('/')
384         
385         f = None
386         if self.srcpath:
387             f = open(self.srcpath) if self.srcpath != '-' else stdin
388         
389         if self.use_hashes and not f:
390             raise Fault('Illegal option combination')
391         if self.public not in ['True', 'False', None]:
392             raise Fault('Not acceptable value for public')
393         public = eval(self.public) if self.public else None
394         self.client.create_object(container, object, f, chunked=self.chunked,
395                                   headers=headers, use_hashes=self.use_hashes,
396                                   public=public, **meta)
397         if f:
398             f.close()
399
400 @cli_command('copy', 'cp')
401 class CopyObject(Command):
402     syntax = '<src container>/<src object> [<dst container>/]<dst object>'
403     description = 'copy an object to a different location'
404     
405     def add_options(self, parser):
406         parser.add_option('--version', action='store',
407                           dest='version', default=False,
408                           help='copy specific version')
409         parser.add_option('--public', action='store',
410                           dest='public', default=None,
411                           help='publish/unpublish object (\'True\'/\'False\')')
412     
413     def execute(self, src, dst):
414         src_container, sep, src_object = src.partition('/')
415         dst_container, sep, dst_object = dst.partition('/')
416         if not sep:
417             dst_container = src_container
418             dst_object = dst
419         version = getattr(self, 'version')
420         if version:
421             headers = {}
422             headers['X_SOURCE_VERSION'] = version
423         if self.public and self.nopublic:
424             raise Fault('Conflicting options')
425         if self.public not in ['True', 'False', None]:
426             raise Fault('Not acceptable value for public')
427         public = eval(self.public) if self.public else None
428         self.client.copy_object(src_container, src_object, dst_container,
429                                 dst_object, public, headers)
430
431 @cli_command('set')
432 class SetMeta(Command):
433     syntax = '[<container>[/<object>]] key=val [key=val] [...]'
434     description = 'set metadata'
435     
436     def execute(self, path, *args):
437         #in case of account fix the args
438         if path.find('=') != -1:
439             args = list(args)
440             args.append(path)
441             args = tuple(args)
442             path = ''
443         meta = {}
444         for arg in args:
445             key, sep, val = arg.partition('=')
446             meta[key.strip()] = val.strip()
447         container, sep, object = path.partition('/')
448         if object:
449             self.client.update_object_metadata(container, object, **meta)
450         elif container:
451             self.client.update_container_metadata(container, **meta)
452         else:
453             self.client.update_account_metadata(**meta)
454
455 @cli_command('update')
456 class UpdateObject(Command):
457     syntax = '<container>/<object> path [key=val] [...]'
458     description = 'update object metadata/data (default mode: append)'
459     
460     def add_options(self, parser):
461         parser.add_option('-a', action='store_true', dest='append',
462                           default=True, help='append data')
463         parser.add_option('--offset', action='store',
464                           dest='offset',
465                           default=None, help='starting offest to be updated')
466         parser.add_option('--range', action='store', dest='content-range',
467                           default=None, help='range of data to be updated')
468         parser.add_option('--chunked', action='store_true', dest='chunked',
469                           default=False, help='set chunked transfer mode')
470         parser.add_option('--content-encoding', action='store',
471                           dest='content-encoding', default=None,
472                           help='provide the object MIME content type')
473         parser.add_option('--content-disposition', action='store', type='str',
474                           dest='content-disposition', default=None,
475                           help='provide the presentation style of the object')
476         parser.add_option('--manifest', action='store', type='str',
477                           dest='manifest', default=None,
478                           help='use for large file support')        
479         parser.add_option('--sharing', action='store',
480                           dest='sharing', default=None,
481                           help='define sharing object policy')
482         parser.add_option('--nosharing', action='store_true',
483                           dest='no_sharing', default=None,
484                           help='clear object sharing policy')
485         parser.add_option('-f', action='store',
486                           dest='srcpath', default=None,
487                           help='file descriptor to read from: pass - for standard input')
488         parser.add_option('--public', action='store',
489                           dest='public', default=None,
490                           help='publish/unpublish object (\'True\'/\'False\')')
491     
492     def execute(self, path, *args):
493         if path.find('=') != -1:
494             raise Fault('Missing path argument')
495         
496         headers = {}
497         if self.manifest:
498             headers['X_OBJECT_MANIFEST'] = self.manifest
499         if self.sharing:
500             headers['X_OBJECT_SHARING'] = self.sharing
501         
502         if self.no_sharing:
503             headers['X_OBJECT_SHARING'] = ''
504         
505         attrs = ['content-encoding', 'content-disposition']
506         attrs = [a for a in attrs if getattr(self, a)]
507         for a in attrs:
508             headers[a.replace('-', '_').upper()] = getattr(self, a)
509         
510         #prepare user defined meta
511         meta = {}
512         for arg in args:
513             key, sep, val = arg.partition('=')
514             meta[key] = val
515         
516         container, sep, object = path.partition('/')
517         
518         f = None
519         chunked = False
520         if self.srcpath:
521             f = self.srcpath != '-' and open(self.srcpath) or stdin
522         if f:
523             chunked = True if (self.chunked or f == stdin) else False
524         if self.public not in ['True', 'False', None]:
525             raise Fault('Not acceptable value for public')
526         public = eval(self.public) if self.public else None
527         self.client.update_object(container, object, f, chunked=chunked,
528                                   headers=headers, offset=self.offset,
529                                   public=public, **meta)
530         if f:
531             f.close()
532
533 @cli_command('move', 'mv')
534 class MoveObject(Command):
535     syntax = '<src container>/<src object> [<dst container>/]<dst object>'
536     description = 'move an object to a different location'
537     
538     def add_options(self, parser):
539         parser.add_option('--public', action='store',
540                           dest='public', default=None,
541                           help='publish/unpublish object (\'True\'/\'False\')')
542     
543     def execute(self, src, dst):
544         src_container, sep, src_object = src.partition('/')
545         dst_container, sep, dst_object = dst.partition('/')
546         if not sep:
547             dst_container = src_container
548             dst_object = dst
549         if self.public not in ['True', 'False', None]:
550             raise Fault('Not acceptable value for public')
551         public = eval(self.public) if self.public else None
552         self.client.move_object(src_container, src_object, dst_container,
553                                 dst_object, public, headers)
554
555 @cli_command('remove')
556 class TrashObject(Command):
557     syntax = '<container>/<object>'
558     description = 'trash an object'
559     
560     def execute(self, src):
561         src_container, sep, src_object = src.partition('/')
562         
563         self.client.trash_object(src_container, src_object)
564
565 @cli_command('restore')
566 class RestoreObject(Command):
567     syntax = '<container>/<object>'
568     description = 'restore a trashed object'
569     
570     def execute(self, src):
571         src_container, sep, src_object = src.partition('/')
572         
573         self.client.restore_object(src_container, src_object)
574
575 @cli_command('unset')
576 class UnsetObject(Command):
577     syntax = '<container>/[<object>] key [key] [...]'
578     description = 'delete metadata info'
579     
580     def execute(self, path, *args):
581         #in case of account fix the args
582         if len(args) == 0:
583             args = list(args)
584             args.append(path)
585             args = tuple(args)
586             path = ''
587         meta = []
588         for key in args:
589             meta.append(key)
590         container, sep, object = path.partition('/')
591         if object:
592             self.client.delete_object_metadata(container, object, meta)
593         elif container:
594             self.client.delete_container_metadata(container, meta)
595         else:
596             self.client.delete_account_metadata(meta)
597
598 @cli_command('group')
599 class SetGroup(Command):
600     syntax = 'key=val [key=val] [...]'
601     description = 'set group account info'
602     
603     def execute(self, *args):
604         groups = {}
605         for arg in args:
606             key, sep, val = arg.partition('=')
607             groups[key] = val
608         self.client.set_account_groups(**groups)
609
610 @cli_command('policy')
611 class SetPolicy(Command):
612     syntax = 'container key=val [key=val] [...]'
613     description = 'set container policies'
614     
615     def execute(self, path, *args):
616         if path.find('=') != -1:
617             raise Fault('Missing container argument')
618         
619         container, sep, object = path.partition('/')
620         
621         if object:
622             raise Fault('Only containers have policies')
623         
624         policies = {}
625         for arg in args:
626             key, sep, val = arg.partition('=')
627             policies[key] = val
628         
629         self.client.set_container_policies(container, **policies)
630
631 @cli_command('publish')
632 class PublishObject(Command):
633     syntax = '<container>/<object>'
634     description = 'publish an object'
635     
636     def execute(self, src):
637         src_container, sep, src_object = src.partition('/')
638         
639         self.client.publish_object(src_container, src_object)
640
641 @cli_command('unpublish')
642 class UnpublishObject(Command):
643     syntax = '<container>/<object>'
644     description = 'unpublish an object'
645     
646     def execute(self, src):
647         src_container, sep, src_object = src.partition('/')
648         
649         self.client.unpublish_object(src_container, src_object)
650
651 def print_usage():
652     cmd = Command('', [])
653     parser = cmd.parser
654     parser.usage = '%prog <command> [options]'
655     parser.print_help()
656     
657     commands = []
658     for cls in set(_cli_commands.values()):
659         name = ', '.join(cls.commands)
660         description = getattr(cls, 'description', '')
661         commands.append('  %s %s' % (name.ljust(12), description))
662     print '\nCommands:\n' + '\n'.join(sorted(commands))
663
664 def print_dict(d, header='name', f=stdout, detail=True):
665     header = header in d and header or 'subdir'
666     if header and header in d:
667         f.write('%s\n' %d.pop(header))
668     if detail:
669         patterns = ['^x_(account|container|object)_meta_(\w+)$']
670         patterns.append(patterns[0].replace('_', '-'))
671         for key, val in sorted(d.items()):
672             for p in patterns:
673                 p = re.compile(p)
674                 m = p.match(key)
675                 if m:
676                     key = m.group(2)
677             f.write('%s: %s\n' % (key.rjust(30), val))
678
679 def print_list(l, verbose=False, f=stdout, detail=True):
680     for elem in l:
681         #if it's empty string continue
682         if not elem:
683             continue
684         if type(elem) == types.DictionaryType:
685             print_dict(elem, f=f, detail=detail)
686         elif type(elem) == types.StringType:
687             if not verbose:
688                 elem = elem.split('Traceback')[0]
689             f.write('%s\n' % elem)
690         else:
691             f.write('%s\n' % elem)
692
693 def print_versions(data, f=stdout):
694     if 'versions' not in data:
695         f.write('%s\n' %data)
696         return
697     f.write('versions:\n')
698     for id, t in data['versions']:
699         f.write('%s @ %s\n' % (str(id).rjust(30), datetime.fromtimestamp(t)))
700
701 def _get_user():
702         try:
703             return os.environ['PITHOS_USER']
704         except KeyError:
705             return getuser()
706
707 def _get_auth():
708         try:
709             return os.environ['PITHOS_AUTH']
710         except KeyError:
711             return '0000'
712     
713
714 def main():
715     try:
716         name = argv[1]
717         cls = class_for_cli_command(name)
718     except (IndexError, KeyError):
719         print_usage()
720         exit(1)
721     
722     cmd = cls(name, argv[2:])
723     
724     try:
725         cmd.execute(*cmd.args)
726     except TypeError, e:
727         cmd.parser.print_help()
728         exit(1)
729     except Fault, f:
730         status = f.status and '%s ' % f.status or ''
731         print '%s%s' % (status, f.data)
732
733 if __name__ == '__main__':
734     main()