Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-tools / pithos / tools / sh.py @ db117cac

History | View | Annotate | Download (32.9 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
    
147
    def execute(self, container=None):
148
        if container:
149
            self.list_objects(container)
150
        else:
151
            self.list_containers()
152
    
153
    def list_containers(self):
154
        attrs = ['limit', 'marker', 'if_modified_since',
155
                 'if_unmodified_since']
156
        args = self._build_args(attrs)
157
        args['format'] = 'json' if self.detail else 'text'
158
        
159
        if getattr(self, 'until'):
160
            t = _time.strptime(self.until, self.format)
161
            args['until'] = int(_time.mktime(t))
162
        
163
        l = self.client.list_containers(**args)
164
        print_list(l)
165
    
166
    def list_objects(self, container):
167
        #prepate params
168
        params = {}
169
        attrs = ['limit', 'marker', 'prefix', 'delimiter', 'path',
170
                 'meta', 'if_modified_since', 'if_unmodified_since']
171
        args = self._build_args(attrs)
172
        args['format'] = 'json' if self.detail else 'text'
173
        
174
        if self.until:
175
            t = _time.strptime(self.until, self.format)
176
            args['until'] = int(_time.mktime(t))
177
        
178
        container, sep, object = container.partition('/')
179
        if object:
180
            return
181
        
182
        detail = 'json'
183
        #if request with meta quering disable trash filtering
184
        show_trashed = True if self.meta else False
185
        l = self.client.list_objects(container, **args)
186
        print_list(l, detail=self.detail)
187

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

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

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

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

    
360
@cli_command('mkdir')
361
class PutMarker(Command):
362
    syntax = '<container>/<directory marker>'
363
    description = 'create a directory marker'
364
    
365
    def execute(self, path):
366
        container, sep, object = path.partition('/')
367
        self.client.create_directory_marker(container, object)
368

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

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

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

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

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

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

    
651
@cli_command('group')
652
class CreateGroup(Command):
653
    syntax = 'key=val [key=val] [...]'
654
    description = 'create account groups'
655
    
656
    def execute(self, *args):
657
        groups = {}
658
        for arg in args:
659
            key, sep, val = arg.partition('=')
660
            groups[key] = val
661
        self.client.set_account_groups(**groups)
662

    
663
@cli_command('ungroup')
664
class DeleteGroup(Command):
665
    syntax = 'key [key] [...]'
666
    description = 'delete account groups'
667
    
668
    def execute(self, *args):
669
        groups = []
670
        for arg in args:
671
            groups.append(arg)
672
        self.client.unset_account_groups(groups)
673

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

    
695
@cli_command('publish')
696
class PublishObject(Command):
697
    syntax = '<container>/<object>'
698
    description = 'publish an object'
699
    
700
    def execute(self, src):
701
        src_container, sep, src_object = src.partition('/')
702
        
703
        self.client.publish_object(src_container, src_object)
704

    
705
@cli_command('unpublish')
706
class UnpublishObject(Command):
707
    syntax = '<container>/<object>'
708
    description = 'unpublish an object'
709
    
710
    def execute(self, src):
711
        src_container, sep, src_object = src.partition('/')
712
        
713
        self.client.unpublish_object(src_container, src_object)
714

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

    
737
@cli_command('send')
738
class Send(Command):
739
    syntax = '<file> <container>[/<prefix>]'
740
    description = 'upload file to container (using prefix)'
741
    
742
    def execute(self, file, path):
743
        container, sep, prefix = path.partition('/')
744
        upload(self.client, file, container, prefix)
745

    
746
@cli_command('receive')
747
class Receive(Command):
748
    syntax = '<container>/<object> <file>'
749
    description = 'download object to file'
750
    
751
    def execute(self, path, file):
752
        container, sep, object = path.partition('/')
753
        download(self.client, container, object, file)
754

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

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

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

    
792
def print_versions(data, f=stdout):
793
    if 'versions' not in data:
794
        f.write('%s\n' %data)
795
        return
796
    f.write('versions:\n')
797
    for id, t in data['versions']:
798
        f.write('%s @ %s\n' % (str(id).rjust(30), datetime.fromtimestamp(float(t))))
799

    
800

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

    
820

    
821
if __name__ == '__main__':
822
    main()