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