Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-tools / pithos / tools / sh.py @ 5ddd6bcb

History | View | Annotate | Download (33.2 kB)

1
#!/usr/bin/env python
2

    
3
# Copyright 2011-2012 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 datetime import datetime
41

    
42
from pithos.tools.lib.client import Pithos_Client, Fault
43
from pithos.tools.lib.util import get_user, get_auth, get_url
44
from pithos.tools.lib.transfer import upload, download
45

    
46
import json
47
import logging
48
import types
49
import re
50
import time as _time
51
import os
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('--url', dest='url', metavar='URL',
72
                          default=get_url(), help='server URL (currently: %s)' % get_url())
73
        parser.add_option('--user', dest='user', metavar='USER',
74
                          default=get_user(),
75
                          help='account USER (currently: %s)' % get_user())
76
        parser.add_option('--token', dest='token', metavar='TOKEN',
77
                          default=get_auth(),
78
                          help='account TOKEN (currently: %s)' % get_auth())
79
        parser.add_option('-v', action='store_true', dest='verbose',
80
                          default=False, help='verbose output')
81
        parser.add_option('-d', action='store_true', dest='debug',
82
                          default=False, help='debug output')
83
        self.add_options(parser)
84
        options, args = parser.parse_args(argv)
85
        
86
        # Add options to self
87
        for opt in parser.option_list:
88
            key = opt.dest
89
            if key:
90
                val = getattr(options, key)
91
                setattr(self, key, val)
92
        
93
        self.client = Pithos_Client(self.url, self.token, self.user, self.verbose,
94
                             self.debug)
95
        
96
        self.parser = parser
97
        self.args = args
98
    
99
    def _build_args(self, attrs):
100
        args = {}
101
        for a in [a for a in attrs if getattr(self, a)]:
102
            args[a] = getattr(self, a)
103
        return args
104

    
105
    def add_options(self, parser):
106
        pass
107
    
108
    def execute(self, *args):
109
        pass
110

    
111
@cli_command('list', 'ls')
112
class List(Command):
113
    syntax = '[<container>[/<object>]]'
114
    description = 'list containers or objects'
115
    
116
    def add_options(self, parser):
117
        parser.add_option('-l', action='store_true', dest='detail',
118
                          default=False, help='show detailed output')
119
        parser.add_option('-n', action='store', type='int', dest='limit',
120
                          default=10000, help='show limited output')
121
        parser.add_option('--marker', action='store', type='str',
122
                          dest='marker', default=None,
123
                          help='show output greater then marker')
124
        parser.add_option('--prefix', action='store', type='str',
125
                          dest='prefix', default=None,
126
                          help='show output starting with prefix')
127
        parser.add_option('--delimiter', action='store', type='str',
128
                          dest='delimiter', default=None,
129
                          help='show output up to the delimiter')
130
        parser.add_option('--path', action='store', type='str',
131
                          dest='path', default=None,
132
                          help='show output starting with prefix up to /')
133
        parser.add_option('--meta', action='store', type='str',
134
                          dest='meta', default=None,
135
                          help='show output having the specified meta keys')
136
        parser.add_option('--if-modified-since', action='store', type='str',
137
                          dest='if_modified_since', default=None,
138
                          help='show output if modified since then')
139
        parser.add_option('--if-unmodified-since', action='store', type='str',
140
                          dest='if_unmodified_since', default=None,
141
                          help='show output if not modified since then')
142
        parser.add_option('--until', action='store', dest='until',
143
                          default=None, help='show metadata until that date')
144
        parser.add_option('--format', action='store', dest='format',
145
                          default='%d/%m/%Y', help='format to parse until date')
146
        parser.add_option('--shared', action='store_true', dest='shared',
147
                          default=False, help='show only shared')
148
        parser.add_option('--public', action='store_true', dest='public',
149
                          default=False, help='show only public')
150
        
151
    
152
    def execute(self, container=None):
153
        if container:
154
            self.list_objects(container)
155
        else:
156
            self.list_containers()
157
    
158
    def list_containers(self):
159
        attrs = ['limit', 'marker', 'if_modified_since',
160
                 'if_unmodified_since', 'shared', 'public']
161
        args = self._build_args(attrs)
162
        args['format'] = 'json' if self.detail else 'text'
163
        
164
        if getattr(self, 'until'):
165
            t = _time.strptime(self.until, self.format)
166
            args['until'] = int(_time.mktime(t))
167
        
168
        l = self.client.list_containers(**args)
169
        print_list(l)
170
    
171
    def list_objects(self, container):
172
        #prepate params
173
        params = {}
174
        attrs = ['limit', 'marker', 'prefix', 'delimiter', 'path',
175
                 'meta', 'if_modified_since', 'if_unmodified_since',
176
                 'shared', 'public']
177
        args = self._build_args(attrs)
178
        args['format'] = 'json' if self.detail else 'text'
179
        
180
        if self.until:
181
            t = _time.strptime(self.until, self.format)
182
            args['until'] = int(_time.mktime(t))
183
        
184
        container, sep, object = container.partition('/')
185
        if object:
186
            return
187
        
188
        detail = 'json'
189
        #if request with meta quering disable trash filtering
190
        show_trashed = True if self.meta else False
191
        l = self.client.list_objects(container, **args)
192
        print_list(l, detail=self.detail)
193

    
194
@cli_command('meta')
195
class Meta(Command):
196
    syntax = '[<container>[/<object>]]'
197
    description = 'get account/container/object metadata'
198
    
199
    def add_options(self, parser):
200
        parser.add_option('-r', action='store_true', dest='restricted',
201
                          default=False, help='show only user defined metadata')
202
        parser.add_option('--until', action='store', dest='until',
203
                          default=None, help='show metadata until that date')
204
        parser.add_option('--format', action='store', dest='format',
205
                          default='%d/%m/%Y', help='format to parse until date')
206
        parser.add_option('--version', action='store', dest='version',
207
                          default=None, help='show specific version \
208
                                  (applies only for objects)')
209
    
210
    def execute(self, path=''):
211
        container, sep, object = path.partition('/')
212
        args = {'restricted': self.restricted}
213
        if getattr(self, 'until'):
214
            t = _time.strptime(self.until, self.format)
215
            args['until'] = int(_time.mktime(t))
216
        
217
        if object:
218
            meta = self.client.retrieve_object_metadata(container, object,
219
                                                        self.restricted,
220
                                                        self.version)
221
        elif container:
222
            meta = self.client.retrieve_container_metadata(container, **args)
223
        else:
224
            meta = self.client.retrieve_account_metadata(**args)
225
        if meta == None:
226
            print 'Entity does not exist'
227
        else:
228
            print_dict(meta, header=None)
229

    
230
@cli_command('create')
231
class CreateContainer(Command):
232
    syntax = '<container> [key=val] [...]'
233
    description = 'create a container'
234
    
235
    def add_options(self, parser):
236
        parser.add_option('--versioning', action='store', dest='versioning',
237
                          default=None, help='set container versioning (auto/none)')
238
        parser.add_option('--quota', action='store', dest='quota',
239
                          default=None, help='set default container quota')
240
    
241
    def execute(self, container, *args):
242
        meta = {}
243
        for arg in args:
244
            key, sep, val = arg.partition('=')
245
            meta[key] = val
246
        policy = {}
247
        if getattr(self, 'versioning'):
248
            policy['versioning'] = self.versioning
249
        if getattr(self, 'quota'):
250
            policy['quota'] = self.quota
251
        ret = self.client.create_container(container, meta=meta, policies=policy)
252
        if not ret:
253
            print 'Container already exists'
254

    
255
@cli_command('delete', 'rm')
256
class Delete(Command):
257
    syntax = '<container>[/<object>]'
258
    description = 'delete a container or an object'
259
    
260
    def add_options(self, parser):
261
        parser.add_option('--until', action='store', dest='until',
262
                          default=None, help='remove history until that date')
263
        parser.add_option('--format', action='store', dest='format',
264
                          default='%d/%m/%Y', help='format to parse until date')
265
        parser.add_option('--delimiter', action='store', type='str',
266
                          dest='delimiter', default=None,
267
                          help='mass delete objects with path staring with <src object> + delimiter')
268
        parser.add_option('-r', action='store_true',
269
                          dest='recursive', default=False,
270
                          help='mass delimiter objects with delimiter /')
271
    
272
    def execute(self, path):
273
        container, sep, object = path.partition('/')
274
        until = None
275
        if getattr(self, 'until'):
276
            t = _time.strptime(self.until, self.format)
277
            until = int(_time.mktime(t))
278
        
279
        if object:
280
            kwargs = {}
281
            if self.delimiter:
282
                kwargs['delimiter'] = self.delimiter
283
            elif self.recursive:
284
                kwargs['delimiter'] = '/'
285
            self.client.delete_object(container, object, until, **kwargs)
286
        else:
287
            self.client.delete_container(container, until)
288

    
289
@cli_command('get')
290
class GetObject(Command):
291
    syntax = '<container>/<object>'
292
    description = 'get the data of an object'
293
    
294
    def add_options(self, parser):
295
        parser.add_option('-l', action='store_true', dest='detail',
296
                          default=False, help='show detailed output')
297
        parser.add_option('--range', action='store', dest='range',
298
                          default=None, help='show range of data')
299
        parser.add_option('--if-range', action='store', dest='if_range',
300
                          default=None, help='show range of data')
301
        parser.add_option('--if-match', action='store', dest='if_match',
302
                          default=None, help='show output if ETags match')
303
        parser.add_option('--if-none-match', action='store',
304
                          dest='if_none_match', default=None,
305
                          help='show output if ETags don\'t match')
306
        parser.add_option('--if-modified-since', action='store', type='str',
307
                          dest='if_modified_since', default=None,
308
                          help='show output if modified since then')
309
        parser.add_option('--if-unmodified-since', action='store', type='str',
310
                          dest='if_unmodified_since', default=None,
311
                          help='show output if not modified since then')
312
        parser.add_option('-o', action='store', type='str',
313
                          dest='file', default=None,
314
                          help='save output in file')
315
        parser.add_option('--version', action='store', type='str',
316
                          dest='version', default=None,
317
                          help='get the specific \
318
                               version')
319
        parser.add_option('--versionlist', action='store_true',
320
                          dest='versionlist', default=False,
321
                          help='get the full object version list')
322
        parser.add_option('--hashmap', action='store_true',
323
                          dest='hashmap', default=False,
324
                          help='get the object hashmap instead')
325
    
326
    def execute(self, path):
327
        attrs = ['if_match', 'if_none_match', 'if_modified_since',
328
                 'if_unmodified_since', 'hashmap']
329
        args = self._build_args(attrs)
330
        args['format'] = 'json' if self.detail else 'text'
331
        if self.range:
332
            args['range'] = 'bytes=%s' % self.range
333
        if getattr(self, 'if_range'):
334
            args['if-range'] = 'If-Range:%s' % getattr(self, 'if_range')
335
        
336
        container, sep, object = path.partition('/')
337
        data = None
338
        if self.versionlist:
339
            if 'detail' in args.keys():
340
                args.pop('detail')
341
            args.pop('format')
342
            self.detail = True
343
            data = self.client.retrieve_object_versionlist(container, object, **args)
344
        elif self.version:
345
            data = self.client.retrieve_object_version(container, object,
346
                                                       self.version, **args)
347
        elif self.hashmap:
348
            if 'detail' in args.keys():
349
                args.pop('detail')
350
            args.pop('format')
351
            self.detail = True
352
            data = self.client.retrieve_object_hashmap(container, object, **args)
353
        else:
354
            data = self.client.retrieve_object(container, object, **args)    
355
        
356
        f = open(self.file, 'w') if self.file else stdout
357
        if self.detail or type(data) == types.DictionaryType:
358
            if self.versionlist:
359
                print_versions(data, f=f)
360
            else:
361
                print_dict(data, f=f)
362
        else:
363
            f.write(data)
364
        f.close()
365

    
366
@cli_command('mkdir')
367
class PutMarker(Command):
368
    syntax = '<container>/<directory marker>'
369
    description = 'create a directory marker'
370
    
371
    def execute(self, path):
372
        container, sep, object = path.partition('/')
373
        self.client.create_directory_marker(container, object)
374

    
375
@cli_command('put')
376
class PutObject(Command):
377
    syntax = '<container>/<object> [key=val] [...]'
378
    description = 'create/override object'
379
    
380
    def add_options(self, parser):
381
        parser.add_option('--use_hashes', action='store_true', dest='use_hashes',
382
                          default=False, help='provide hashmap instead of data')
383
        parser.add_option('--chunked', action='store_true', dest='chunked',
384
                          default=False, help='set chunked transfer mode')
385
        parser.add_option('--etag', action='store', dest='etag',
386
                          default=None, help='check written data')
387
        parser.add_option('--content-encoding', action='store',
388
                          dest='content_encoding', default=None,
389
                          help='provide the object MIME content type')
390
        parser.add_option('--content-disposition', action='store', type='str',
391
                          dest='content_disposition', default=None,
392
                          help='provide the presentation style of the object')
393
        #parser.add_option('-S', action='store',
394
        #                  dest='segment_size', default=False,
395
        #                  help='use for large file support')
396
        parser.add_option('--manifest', action='store',
397
                          dest='x_object_manifest', default=None,
398
                          help='provide object parts prefix in <container>/<object> form')
399
        parser.add_option('--content-type', action='store',
400
                          dest='content_type', default=None,
401
                          help='create object with specific content type')
402
        parser.add_option('--sharing', action='store',
403
                          dest='x_object_sharing', default=None,
404
                          help='define sharing object policy')
405
        parser.add_option('-f', action='store',
406
                          dest='srcpath', default=None,
407
                          help='file descriptor to read from (pass - for standard input)')
408
        parser.add_option('--public', action='store_true',
409
                          dest='x_object_public', default=False,
410
                          help='make object publicly accessible')
411
    
412
    def execute(self, path, *args):
413
        if path.find('=') != -1:
414
            raise Fault('Missing path argument')
415
        
416
        #prepare user defined meta
417
        meta = {}
418
        for arg in args:
419
            key, sep, val = arg.partition('=')
420
            meta[key] = val
421
        
422
        attrs = ['etag', 'content_encoding', 'content_disposition',
423
                 'content_type', 'x_object_sharing', 'x_object_public']
424
        args = self._build_args(attrs)
425
        
426
        container, sep, object = path.partition('/')
427
        
428
        f = None
429
        if self.srcpath:
430
            f = open(self.srcpath) if self.srcpath != '-' else stdin
431
        
432
        if self.use_hashes and not f:
433
            raise Fault('Illegal option combination')
434
        
435
        if self.chunked:
436
            self.client.create_object_using_chunks(container, object, f,
437
                                                    meta=meta, **args)
438
        elif self.use_hashes:
439
            data = f.read()
440
            hashmap = json.loads(data)
441
            self.client.create_object_by_hashmap(container, object, hashmap,
442
                                             meta=meta, **args)
443
        elif self.x_object_manifest:
444
            self.client.create_manifestation(container, object, self.x_object_manifest)
445
        elif not f:
446
            self.client.create_zero_length_object(container, object, meta=meta, **args)
447
        else:
448
            self.client.create_object(container, object, f, meta=meta, **args)
449
        if f:
450
            f.close()
451

    
452
@cli_command('copy', 'cp')
453
class CopyObject(Command):
454
    syntax = '<src container>/<src object> [<dst container>/]<dst object> [key=val] [...]'
455
    description = 'copy an object to a different location'
456
    
457
    def add_options(self, parser):
458
        parser.add_option('--version', action='store',
459
                          dest='version', default=False,
460
                          help='copy specific version')
461
        parser.add_option('--public', action='store_true',
462
                          dest='public', default=False,
463
                          help='make object publicly accessible')
464
        parser.add_option('--content-type', action='store',
465
                          dest='content_type', default=None,
466
                          help='change object\'s content type')
467
        parser.add_option('--delimiter', action='store', type='str',
468
                          dest='delimiter', default=None,
469
                          help='mass copy objects with path staring with <src object> + delimiter')
470
        parser.add_option('-r', action='store_true',
471
                          dest='recursive', default=False,
472
                          help='mass copy with delimiter /')
473
    
474
    def execute(self, src, dst, *args):
475
        src_container, sep, src_object = src.partition('/')
476
        dst_container, sep, dst_object = dst.partition('/')
477
        
478
        #prepare user defined meta
479
        meta = {}
480
        for arg in args:
481
            key, sep, val = arg.partition('=')
482
            meta[key] = val
483
        
484
        if not sep:
485
            dst_container = src_container
486
            dst_object = dst
487
        
488
        args = {'content_type':self.content_type} if self.content_type else {}
489
        if self.delimiter:
490
                args['delimiter'] = self.delimiter
491
        elif self.recursive:
492
                args['delimiter'] = '/'
493
        self.client.copy_object(src_container, src_object, dst_container,
494
                                dst_object, meta, self.public, self.version,
495
                                **args)
496

    
497
@cli_command('set')
498
class SetMeta(Command):
499
    syntax = '[<container>[/<object>]] key=val [key=val] [...]'
500
    description = 'set account/container/object metadata'
501
    
502
    def execute(self, path, *args):
503
        #in case of account fix the args
504
        if path.find('=') != -1:
505
            args = list(args)
506
            args.append(path)
507
            args = tuple(args)
508
            path = ''
509
        meta = {}
510
        for arg in args:
511
            key, sep, val = arg.partition('=')
512
            meta[key.strip()] = val.strip()
513
        container, sep, object = path.partition('/')
514
        if object:
515
            self.client.update_object_metadata(container, object, **meta)
516
        elif container:
517
            self.client.update_container_metadata(container, **meta)
518
        else:
519
            self.client.update_account_metadata(**meta)
520

    
521
@cli_command('update')
522
class UpdateObject(Command):
523
    syntax = '<container>/<object> path [key=val] [...]'
524
    description = 'update object metadata/data (default mode: append)'
525
    
526
    def add_options(self, parser):
527
        parser.add_option('-a', action='store_true', dest='append',
528
                          default=True, help='append data')
529
        parser.add_option('--offset', action='store',
530
                          dest='offset',
531
                          default=None, help='starting offest to be updated')
532
        parser.add_option('--range', action='store', dest='content_range',
533
                          default=None, help='range of data to be updated')
534
        parser.add_option('--chunked', action='store_true', dest='chunked',
535
                          default=False, help='set chunked transfer mode')
536
        parser.add_option('--content-encoding', action='store',
537
                          dest='content_encoding', default=None,
538
                          help='provide the object MIME content type')
539
        parser.add_option('--content-disposition', action='store', type='str',
540
                          dest='content_disposition', default=None,
541
                          help='provide the presentation style of the object')
542
        parser.add_option('--manifest', action='store', type='str',
543
                          dest='x_object_manifest', default=None,
544
                          help='use for large file support')        
545
        parser.add_option('--sharing', action='store',
546
                          dest='x_object_sharing', default=None,
547
                          help='define sharing object policy')
548
        parser.add_option('--nosharing', action='store_true',
549
                          dest='no_sharing', default=None,
550
                          help='clear object sharing policy')
551
        parser.add_option('-f', action='store',
552
                          dest='srcpath', default=None,
553
                          help='file descriptor to read from: pass - for standard input')
554
        parser.add_option('--public', action='store_true',
555
                          dest='x_object_public', default=False,
556
                          help='make object publicly accessible')
557
        parser.add_option('--replace', action='store_true',
558
                          dest='replace', default=False,
559
                          help='override metadata')
560
    
561
    def execute(self, path, *args):
562
        if path.find('=') != -1:
563
            raise Fault('Missing path argument')
564
        
565
        #prepare user defined meta
566
        meta = {}
567
        for arg in args:
568
            key, sep, val = arg.partition('=')
569
            meta[key] = val
570
        
571
        
572
        attrs = ['content_encoding', 'content_disposition', 'x_object_sharing',
573
                 'x_object_public', 'x_object_manifest', 'replace', 'offset',
574
                 'content_range']
575
        args = self._build_args(attrs)
576
        
577
        if self.no_sharing:
578
            args['x_object_sharing'] = ''
579
        
580
        container, sep, object = path.partition('/')
581
        
582
        f = None
583
        if self.srcpath:
584
            f = open(self.srcpath) if self.srcpath != '-' else stdin
585
        
586
        if self.chunked:
587
            self.client.update_object_using_chunks(container, object, f,
588
                                                    meta=meta, **args)
589
        else:
590
            self.client.update_object(container, object, f, meta=meta, **args)
591
        if f:
592
            f.close()
593

    
594
@cli_command('move', 'mv')
595
class MoveObject(Command):
596
    syntax = '<src container>/<src object> [<dst container>/]<dst object>'
597
    description = 'move an object to a different location'
598
    
599
    def add_options(self, parser):
600
        parser.add_option('--public', action='store_true',
601
                          dest='public', default=False,
602
                          help='make object publicly accessible')
603
        parser.add_option('--content-type', action='store',
604
                          dest='content_type', default=None,
605
                          help='change object\'s content type')
606
        parser.add_option('--delimiter', action='store', type='str',
607
                          dest='delimiter', default=None,
608
                          help='mass move objects with path staring with <src object> + delimiter')
609
        parser.add_option('-r', action='store_true',
610
                          dest='recursive', default=False,
611
                          help='mass move objects with delimiter /')
612
    
613
    def execute(self, src, dst, *args):
614
        src_container, sep, src_object = src.partition('/')
615
        dst_container, sep, dst_object = dst.partition('/')
616
        if not sep:
617
            dst_container = src_container
618
            dst_object = dst
619
        
620
        #prepare user defined meta
621
        meta = {}
622
        for arg in args:
623
            key, sep, val = arg.partition('=')
624
            meta[key] = val
625
        
626
        args = {'content_type':self.content_type} if self.content_type else {}
627
        if self.delimiter:
628
                args['delimiter'] = self.delimiter
629
        elif self.recursive:
630
                args['delimiter'] = '/'
631
        self.client.move_object(src_container, src_object, dst_container,
632
                                dst_object, meta, self.public, **args)
633

    
634
@cli_command('unset')
635
class UnsetObject(Command):
636
    syntax = '<container>/[<object>] key [key] [...]'
637
    description = 'delete metadata info'
638
    
639
    def execute(self, path, *args):
640
        #in case of account fix the args
641
        if len(args) == 0:
642
            args = list(args)
643
            args.append(path)
644
            args = tuple(args)
645
            path = ''
646
        meta = []
647
        for key in args:
648
            meta.append(key)
649
        container, sep, object = path.partition('/')
650
        if object:
651
            self.client.delete_object_metadata(container, object, meta)
652
        elif container:
653
            self.client.delete_container_metadata(container, meta)
654
        else:
655
            self.client.delete_account_metadata(meta)
656

    
657
@cli_command('group')
658
class CreateGroup(Command):
659
    syntax = 'key=val [key=val] [...]'
660
    description = 'create account groups'
661
    
662
    def execute(self, *args):
663
        groups = {}
664
        for arg in args:
665
            key, sep, val = arg.partition('=')
666
            groups[key] = val
667
        self.client.set_account_groups(**groups)
668

    
669
@cli_command('ungroup')
670
class DeleteGroup(Command):
671
    syntax = 'key [key] [...]'
672
    description = 'delete account groups'
673
    
674
    def execute(self, *args):
675
        groups = []
676
        for arg in args:
677
            groups.append(arg)
678
        self.client.unset_account_groups(groups)
679

    
680
@cli_command('policy')
681
class SetPolicy(Command):
682
    syntax = 'container key=val [key=val] [...]'
683
    description = 'set container policies'
684
    
685
    def execute(self, path, *args):
686
        if path.find('=') != -1:
687
            raise Fault('Missing container argument')
688
        
689
        container, sep, object = path.partition('/')
690
        
691
        if object:
692
            raise Fault('Only containers have policies')
693
        
694
        policies = {}
695
        for arg in args:
696
            key, sep, val = arg.partition('=')
697
            policies[key] = val
698
        
699
        self.client.set_container_policies(container, **policies)
700

    
701
@cli_command('publish')
702
class PublishObject(Command):
703
    syntax = '<container>/<object>'
704
    description = 'publish an object'
705
    
706
    def execute(self, src):
707
        src_container, sep, src_object = src.partition('/')
708
        
709
        self.client.publish_object(src_container, src_object)
710

    
711
@cli_command('unpublish')
712
class UnpublishObject(Command):
713
    syntax = '<container>/<object>'
714
    description = 'unpublish an object'
715
    
716
    def execute(self, src):
717
        src_container, sep, src_object = src.partition('/')
718
        
719
        self.client.unpublish_object(src_container, src_object)
720

    
721
@cli_command('sharing')
722
class SharingObject(Command):
723
    syntax = 'list users sharing objects with the user'
724
    description = 'list user accounts sharing objects with the user'
725
    
726
    def add_options(self, parser):
727
        parser.add_option('-l', action='store_true', dest='detail',
728
                          default=False, help='show detailed output')
729
        parser.add_option('-n', action='store', type='int', dest='limit',
730
                          default=10000, help='show limited output')
731
        parser.add_option('--marker', action='store', type='str',
732
                          dest='marker', default=None,
733
                          help='show output greater then marker')
734
        
735
    
736
    def execute(self):
737
        attrs = ['limit', 'marker']
738
        args = self._build_args(attrs)
739
        args['format'] = 'json' if self.detail else 'text'
740
        
741
        print_list(self.client.list_shared_by_others(**args))
742

    
743
@cli_command('send')
744
class Send(Command):
745
    syntax = '<file> <container>[/<prefix>]'
746
    description = 'upload file to container (using prefix)'
747
    
748
    def execute(self, file, path):
749
        container, sep, prefix = path.partition('/')
750
        upload(self.client, file, container, prefix)
751

    
752
@cli_command('receive')
753
class Receive(Command):
754
    syntax = '<container>/<object> <file>'
755
    description = 'download object to file'
756
    
757
    def execute(self, path, file):
758
        container, sep, object = path.partition('/')
759
        download(self.client, container, object, file)
760

    
761
def print_usage():
762
    cmd = Command('', [])
763
    parser = cmd.parser
764
    parser.usage = '%prog <command> [options]'
765
    parser.print_help()
766
    
767
    commands = []
768
    for cls in set(_cli_commands.values()):
769
        name = ', '.join(cls.commands)
770
        description = getattr(cls, 'description', '')
771
        commands.append('  %s %s' % (name.ljust(12), description))
772
    print '\nCommands:\n' + '\n'.join(sorted(commands))
773

    
774
def print_dict(d, header='name', f=stdout, detail=True):
775
    header = header if header in d else 'subdir'
776
    if header and header in d:
777
        f.write('%s\n' %d.pop(header).encode('utf8'))
778
    if detail:
779
        patterns = ['^x_(account|container|object)_meta_(\w+)$']
780
        patterns.append(patterns[0].replace('_', '-'))
781
        for key, val in sorted(d.items()):
782
            f.write('%s: %s\n' % (key.rjust(30), val))
783

    
784
def print_list(l, verbose=False, f=stdout, detail=True):
785
    for elem in l:
786
        #if it's empty string continue
787
        if not elem:
788
            continue
789
        if type(elem) == types.DictionaryType:
790
            print_dict(elem, f=f, detail=detail)
791
        elif type(elem) == types.StringType:
792
            if not verbose:
793
                elem = elem.split('Traceback')[0]
794
            f.write('%s\n' % elem)
795
        else:
796
            f.write('%s\n' % elem)
797

    
798
def print_versions(data, f=stdout):
799
    if 'versions' not in data:
800
        f.write('%s\n' %data)
801
        return
802
    f.write('versions:\n')
803
    for id, t in data['versions']:
804
        f.write('%s @ %s\n' % (str(id).rjust(30), datetime.fromtimestamp(float(t))))
805

    
806

    
807
def main():
808
    try:
809
        name = argv[1]
810
        cls = class_for_cli_command(name)
811
    except (IndexError, KeyError):
812
        print_usage()
813
        exit(1)
814
    
815
    cmd = cls(name, argv[2:])
816
    
817
    try:
818
        cmd.execute(*cmd.args)
819
    except TypeError, e:
820
        cmd.parser.print_help()
821
        exit(1)
822
    except Fault, f:
823
        status = '%s ' % f.status if f.status else ''
824
        print '%s%s' % (status, f.data)
825

    
826

    
827
if __name__ == '__main__':
828
    main()