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