Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / pithos_cli.py @ a298f2ab

History | View | Annotate | Download (28.8 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.command
33

    
34
from kamaki.cli import command, set_api_description, CLIError
35
from kamaki.clients.utils import filter_in
36
from kamaki.utils import format_size
37
set_api_description('store', 'Pithos+ storage commands')
38
from .pithos import PithosClient, ClientError
39
from .cli_utils import raiseCLIError
40
from kamaki.utils import print_dict, pretty_keys, print_list
41
from colors import bold
42

    
43
from progress.bar import IncrementalBar
44

    
45

    
46
class ProgressBar(IncrementalBar):
47
    suffix = '%(percent)d%% - %(eta)ds'
48

    
49
class _pithos_init(object):
50
    def main(self):
51
        token = self.config.get('store', 'token') or self.config.get('global', 'token')
52
        base_url = self.config.get('store', 'url') or self.config.get('global', 'url')
53
        account = self.config.get('store', 'account') or self.config.get('global', 'account')
54
        container = self.config.get('store', 'container') or self.config.get('global', 'container')
55
        self.client = PithosClient(base_url=base_url, token=token, account=account,
56
            container=container)
57

    
58
class _store_account_command(_pithos_init):
59
    """Base class for account level storage commands"""
60

    
61
    def update_parser(self, parser):
62
        parser.add_argument('--account', dest='account', metavar='NAME',
63
                          help="Specify an account to use")
64

    
65
    def progress(self, message):
66
        """Return a generator function to be used for progress tracking"""
67

    
68
        MESSAGE_LENGTH = 25
69

    
70
        def progress_gen(n):
71
            msg = message.ljust(MESSAGE_LENGTH)
72
            for i in ProgressBar(msg).iter(range(n)):
73
                yield
74
            yield
75

    
76
        return progress_gen
77

    
78
    def main(self):
79
        super(_store_account_command, self).main()
80
        if hasattr(self.args, 'account') and self.args.account is not None:
81
            self.client.account = self.args.account
82

    
83
class _store_container_command(_store_account_command):
84
    """Base class for container level storage commands"""
85

    
86
    def update_parser(self, parser):
87
        super(_store_container_command, self).update_parser(parser)
88
        parser.add_argument('--container', dest='container', metavar='NAME',
89
                          help="Specify a container to use")
90

    
91
    def extract_container_and_path(self, container_with_path):
92
        assert isinstance(container_with_path, str)
93
        cnp = container_with_path.split(':')
94
        self.container = cnp[0]
95
        self.path = cnp[1] if len(cnp) > 1 else None
96
            
97

    
98
    def main(self, container_with_path=None):
99
        super(_store_container_command, self).main()
100
        if container_with_path is not None:
101
            self.extract_container_and_path(container_with_path)
102
            self.client.container = self.container
103
        elif hasattr(self.args, 'container') and self.args.container is not None:
104
            self.client.container = self.args.container
105
        else:
106
            self.container = None
107

    
108
@command()
109
class store_list(_store_container_command):
110
    """List containers, object trees or objects in a directory
111
    """
112

    
113
    def update_parser(self, parser):
114
        super(self.__class__, self).update_parser(parser)
115
        parser.add_argument('-l', action='store_true', dest='detail', default=False,
116
            help='show detailed output')
117
        parser.add_argument('-N', action='store', dest='show_size', default=21,
118
            help='print output in chunks of size N')
119
        parser.add_argument('-n', action='store', dest='limit', default=None,
120
            help='show limited output')
121
        parser.add_argument('--marker', action='store', dest='marker', default=None,
122
            help='show output greater then marker')
123
        parser.add_argument('--prefix', action='store', dest='prefix', default=None,
124
            help='show output starting with prefix')
125
        parser.add_argument('--delimiter', action='store', dest='delimiter', default=None, 
126
            help='show output up to the delimiter')
127
        parser.add_argument('--path', action='store', dest='path', default=None, 
128
            help='show output starting with prefix up to /')
129
        parser.add_argument('--meta', action='store', dest='meta', default=None, 
130
            help='show output having the specified meta keys (e.g. --meta "meta1 meta2 ..."')
131
        parser.add_argument('--if-modified-since', action='store', dest='if_modified_since', 
132
            default=None, help='show output if modified since then')
133
        parser.add_argument('--if-unmodified-since', action='store', dest='if_unmodified_since',
134
            default=None, help='show output if not modified since then')
135
        parser.add_argument('--until', action='store', dest='until', default=None,
136
            help='show metadata until that date')
137
        dateformat = '%d/%m/%Y %H:%M:%S'
138
        parser.add_argument('--format', action='store', dest='format', default=dateformat,
139
            help='format to parse until date (default: d/m/Y H:M:S)')
140
        parser.add_argument('--shared', action='store_true', dest='shared', default=False,
141
            help='show only shared')
142
        parser.add_argument('--public', action='store_true', dest='public', default=False,
143
            help='show only public')
144

    
145
    def print_objects(self, object_list):
146
        import sys
147
        try:
148
            limit = getattr(self.args, 'show_size')
149
            limit = int(limit)
150
        except AttributeError:
151
            pass
152
        index = 0
153
        for obj in object_list:
154
            if not obj.has_key('content_type'):
155
                continue
156
            pretty_obj = obj.copy()
157
            index += 1
158
            empty_space = ' '*(len(object_list)/10 - index/10)
159
            if obj['content_type'] == 'application/directory':
160
                isDir = True
161
                size = 'D'
162
            else:
163
                isDir = False
164
                size = format_size(obj['bytes'])
165
                pretty_obj['bytes'] = '%s (%s)'%(obj['bytes'],size)
166
            oname = bold(obj['name'])
167
            if getattr(self.args, 'detail'):
168
                print('%s%s. %s'%(empty_space, index, oname))
169
                print_dict(pretty_keys(pretty_obj), exclude=('name'))
170
                print
171
            else:
172
                oname = '%s%s. %6s %s'%(empty_space, index, size, oname)
173
                oname += '/' if isDir else ''
174
                print(oname)
175
            if limit <= index < len(object_list) and index%limit == 0:
176
                print('(press "enter" to continue)')
177
                sys.stdin.read(1)
178

    
179
    def print_containers(self, container_list):
180
        import sys
181
        try:
182
            limit = getattr(self.args, 'show_size')
183
            limit = int(limit)
184
        except AttributeError:
185
            pass
186
        for container in container_list:
187
            size = format_size(container['bytes'])
188
            index = 1+container_list.index(container)
189
            cname = '%s. %s'%(index, bold(container['name']))
190
            if getattr(self.args, 'detail'):
191
                print(cname)
192
                pretty_c = container.copy()
193
                pretty_c['bytes'] = '%s (%s)'%(container['bytes'], size)
194
                print_dict(pretty_keys(pretty_c), exclude=('name'))
195
                print
196
            else:
197
                print('%s (%s, %s objects)' % (cname, size, container['count']))
198
            if limit <= index < len(container_list) and index%limit == 0:
199
                print('(press "enter" to continue)')
200
                sys.stdin.read(1)
201

    
202
    def getuntil(self, orelse=None):
203
        if hasattr(self.args, 'until'):
204
            import time
205
            until = getattr(self.args, 'until')
206
            if until is None:
207
                return None
208
            format = getattr(self.args, 'format')
209
            #except TypeError:
210
            try:
211
                t = time.strptime(until, format)
212
            except ValueError as err:
213
                raise CLIError(message='in --until: '+unicode(err), importance=1)
214
            return int(time.mktime(t))
215
        return orelse
216
   
217
    def getmeta(self, orelse=[]):
218
        if hasattr(self.args, 'meta'):
219
            meta = getattr(self.args, 'meta')
220
            if meta is None:
221
                return []
222
            return meta.split(' ')
223
        return orelse
224

    
225
    def main(self, container____path__=None):
226
        super(self.__class__, self).main(container____path__)
227
        try:
228
            if self.container is None:
229
                r = self.client.account_get(limit=getattr(self.args, 'limit', None),
230
                    marker=getattr(self.args, 'marker', None),
231
                    if_modified_since=getattr(self.args, 'if_modified_since', None),
232
                    if_unmodified_since=getattr(self.args, 'if_unmodified_since', None),
233
                    until=self.getuntil(),
234
                    show_only_shared=getattr(self.args, 'shared', False))
235
                self.print_containers(r.json)
236
            else:
237
                r = self.client.container_get(limit=getattr(self.args, 'limit', None),
238
                    marker=getattr(self.args, 'marker', None),
239
                    prefix=getattr(self.args, 'prefix', None),
240
                    delimiter=getattr(self.args, 'delimiter', None),
241
                    path=getattr(self.args, 'path', None) if self.path is None else self.path,
242
                    if_modified_since=getattr(self.args, 'if_modified_since', None),
243
                    if_unmodified_since=getattr(self.args, 'if_unmodified_since', None),
244
                    until=self.getuntil(),
245
                    meta=self.getmeta(),
246
                    show_only_shared=getattr(self.args, 'shared', False))
247
                self.print_objects(r.json)
248
        except ClientError as err:
249
            raiseCLIError(err)
250

    
251
@command()
252
class store_mkdir(_store_container_command):
253
    """Create a directory"""
254

    
255
    def main(self, directory):
256
        super(self.__class__, self).main()
257
        try:
258
            self.client.create_directory(directory)
259
        except ClientError as err:
260
            raiseCLIError(err)
261

    
262
@command()
263
class store_create(_store_container_command):
264
    """Create a container or a directory object"""
265

    
266
    def update_parser(self, parser):
267
        parser.add_argument('--versioning', action='store', dest='versioning', default=None,
268
            help='set container versioning (auto/none)')
269
        parser.add_argument('--quota', action='store', dest='quota', default=None,
270
            help='set default container quota')
271
        parser.add_argument('--meta', action='store', dest='meta', default=None,
272
            help='set container metadata ("key1:val1 key2:val2 ...")')
273

    
274
    def getmeta(self, orelse=None):
275
        try:
276
            meta = getattr(self.args,'meta')
277
            metalist = meta.split(' ')
278
        except AttributeError:
279
            return orelse
280
        metadict = {}
281
        for metastr in metalist:
282
            (key,val) = metastr.split(':')
283
            metadict[key] = val
284
        return metadict
285

    
286
    def main(self, container____directory__):
287
        super(self.__class__, self).main(container____directory__)
288
        try:
289
            if self.path is None:
290
                self.client.container_put(quota=getattr(self.args, 'quota'),
291
                    versioning=getattr(self.args, 'versioning'), metadata=self.getmeta())
292
            else:
293
                self.client.create_directory(self.path)
294
        except ClientError as err:
295
            raiseCLIError(err)
296

    
297
@command()
298
class store_copy(_store_container_command):
299
    """Copy an object"""
300

    
301
    def main(self, source_container___path, destination_container____path__):
302
        super(self.__class__, self).main(source_container___path)
303
        try:
304
            dst = destination_container____path__.split(':')
305
            dst_cont = dst[0]
306
            dst_path = dst[1] if len(dst) > 1 else False
307
            self.client.copy_object(src_container = self.container, src_object = self.path,
308
                dst_container = dst_cont, dst_object = dst_path)
309
        except ClientError as err:
310
            raiseCLIError(err)
311

    
312
@command()
313
class store_move(_store_container_command):
314
    """Move an object"""
315

    
316
    def main(self, source_container___path, destination_container____path__):
317
        super(self.__class__, self).main(source_container___path)
318
        try:
319
            dst = destination_container____path__.split(':')
320
            dst_cont = dst[0]
321
            dst_path = dst[1] if len(dst) > 1 else False
322
            self.client.move_object(src_container = self.container, src_object = self.path,
323
                dst_container = dst_cont, dst_object = dst_path)
324
        except ClientError as err:
325
            raiseCLIError(err)
326

    
327
@command()
328
class store_append(_store_container_command):
329
    """Append local file to (existing) remote object"""
330

    
331
    
332
    def main(self, local_path, container___path):
333
        super(self.__class__, self).main(container___path)
334
        try:
335
            f = open(local_path, 'r')
336
            upload_cb = self.progress('Appending blocks')
337
            self.client.append_object(object=self.path, source_file = f, upload_cb = upload_cb)
338
        except ClientError as err:
339
            raiseCLIError(err)
340

    
341
@command()
342
class store_truncate(_store_container_command):
343
    """Truncate remote file up to a size"""
344

    
345
    
346
    def main(self, container___path, size=0):
347
        super(self.__class__, self).main(container___path)
348
        try:
349
            self.client.truncate_object(self.path, size)
350
        except ClientError as err:
351
            raiseCLIError(err)
352

    
353
@command()
354
class store_overwrite(_store_container_command):
355
    """Overwrite part (from start to end) of a remote file"""
356

    
357
    def main(self, local_path, container___path, start, end):
358
        super(self.__class__, self).main(container___path)
359
        try:
360
            f = open(local_path, 'r')
361
            upload_cb = self.progress('Overwritting blocks')
362
            self.client.overwrite_object(object=self.path, start=start, end=end,
363
                source_file=f, upload_cb = upload_cb)
364
        except ClientError as err:
365
            raiseCLIError(err)
366

    
367
@command()
368
class store_upload(_store_container_command):
369
    """Upload a file"""
370

    
371
    def main(self, local_path, container____path__):
372
        super(self.__class__, self).main(container____path__)
373
        try:
374
            remote_path = basename(local_path) if self.path is None else self.path
375
            with open(local_path) as f:
376
                hash_cb = self.progress('Calculating block hashes')
377
                upload_cb = self.progress('Uploading blocks')
378
                self.client.async_upload_object(remote_path, f, hash_cb=hash_cb, upload_cb=upload_cb)
379
        except ClientError as err:
380
            raiseCLIError(err)
381

    
382
@command()
383
class store_download(_store_container_command):
384
    """Download a file"""
385

    
386
    
387
    def main(self, container___path, local_path='-'):
388
        super(self.__class__, self).main(container___path)
389
        try:
390
            f, size = self.client.get_object(self.path)
391
        except ClientError as err:
392
            raiseCLIError(err)
393
        try:
394
            out = open(local_path, 'w') if local_path != '-' else stdout
395
        except IOError:
396
            raise CLIError(message='Cannot write to file %s'%local_path, importance=1)
397

    
398
        blocksize = 4 * 1024 ** 2
399
        nblocks = 1 + (size - 1) // blocksize
400

    
401
        cb = self.progress('Downloading blocks') if local_path != '-' else None
402
        if cb:
403
            gen = cb(nblocks)
404
            gen.next()
405

    
406
        data = f.read(blocksize)
407
        while data:
408
            out.write(data)
409
            data = f.read(blocksize)
410
            if cb:
411
                gen.next()
412

    
413
@command()
414
class store_delete(_store_container_command):
415
    """Delete a container [or an object]"""
416

    
417
    def update_parser(self, parser):
418
        parser.add_argument('--until', action='store', dest='until', default=None,
419
            help='remove history until that date')
420
        parser.add_argument('--format', action='store', dest='format', default='%d/%m/%Y %H:%M:%S',
421
            help='format to parse until date (default: d/m/Y H:M:S)')
422
        parser.add_argument('--delimiter', action='store', dest='delimiter',
423
            default=None, 
424
            help='mass delete objects with path staring with <object><delimiter>')
425
        parser.add_argument('-r', action='store_true', dest='recursive', default=False,
426
            help='empty dir or container and delete (if dir)')
427
    
428
    def getuntil(self, orelse=None):
429
        if hasattr(self.args, 'until'):
430
            import time
431
            until = getattr(self.args, 'until')
432
            if until is None:
433
                return None
434
            format = getattr(self.args, 'format')
435
            try:
436
                t = time.strptime(until, format)
437
            except ValueError as err:
438
                raise CLIError(message='in --until: '+unicode(err), importance=1)
439
            return int(time.mktime(t))
440
        return orelse
441

    
442
    def getdelimiter(self, orelse=None):
443
        try:
444
            dlm = getattr(self.args, 'delimiter')
445
            if dlm is None:
446
                return '/' if getattr(self.args, 'recursive') else orelse
447
        except AttributeError:
448
            return orelse
449
        return dlm
450

    
451
    def main(self, container____path__):
452
        super(self.__class__, self).main(container____path__)
453
        try:
454
            if self.path is None:
455
                self.client.del_container(until=self.getuntil(), delimiter=self.getdelimiter())
456
            else:
457
                #self.client.delete_object(self.path)
458
                self.client.del_object(self.path, until=self.getuntil(),
459
                    delimiter=self.getdelimiter())
460
        except ClientError as err:
461
            raiseCLIError(err)
462

    
463
@command()
464
class store_purge(_store_account_command):
465
    """Purge a container"""
466
    
467
    def main(self, container):
468
        super(self.__class__, self).main()
469
        try:
470
            self.client.container = container
471
            self.client.purge_container()
472
        except ClientError as err:
473
            raiseCLIError(err)
474

    
475
@command()
476
class store_publish(_store_container_command):
477
    """Publish an object"""
478

    
479
    def main(self, container___path):
480
        super(self.__class__, self).main(container___path)
481
        try:
482
            self.client.publish_object(self.path)
483
        except ClientError as err:
484
            raiseCLIError(err)
485

    
486
@command()
487
class store_unpublish(_store_container_command):
488
    """Unpublish an object"""
489

    
490
    def main(self, container___path):
491
        super(self.__class__, self).main(container___path)
492
        try:
493
            self.client.unpublish_object(self.path)
494
        except ClientError as err:
495
            raiseCLIError(err)
496

    
497
@command()
498
class store_permitions(_store_container_command):
499
    """Get object read/write permitions"""
500

    
501
    def main(self, container___path):
502
        super(self.__class__, self).main(container___path)
503
        try:
504
            reply = self.client.get_object_sharing(self.path)
505
            print_dict(reply)
506
        except ClientError as err:
507
            raiseCLIError(err)
508

    
509
@command()
510
class store_setpermitions(_store_container_command):
511
    """Set sharing permitions"""
512

    
513
    def main(self, container___path, *permitions):
514
        super(self.__class__, self).main(container___path)
515
        read = False
516
        write = False
517
        for perms in permitions:
518
            splstr = perms.split('=')
519
            if 'read' == splstr[0]:
520
                read = [user_or_group.strip() \
521
                for user_or_group in splstr[1].split(',')]
522
            elif 'write' == splstr[0]:
523
                write = [user_or_group.strip() \
524
                for user_or_group in splstr[1].split(',')]
525
            else:
526
                read = False
527
                write = False
528
        if not read and not write:
529
            raise CLIError(message='Usage:\tread=<groups,users> write=<groups,users>',
530
                importance=0)
531
        try:
532
            self.client.set_object_sharing(self.path,
533
                read_permition=read, write_permition=write)
534
        except ClientError as err:
535
            raiseCLIError(err)
536

    
537
@command()
538
class store_delpermitions(_store_container_command):
539
    """Delete all sharing permitions"""
540

    
541
    def main(self, container___path):
542
        super(self.__class__, self).main(container___path)
543
        try:
544
            self.client.del_object_sharing(self.path)
545
        except ClientError as err:
546
            raiseCLIError(err)
547

    
548
@command()
549
class store_info(_store_container_command):
550
    """Get information for account [, container [or object]]"""
551

    
552
    
553
    def main(self, container____path__=None):
554
        super(self.__class__, self).main(container____path__)
555
        try:
556
            if self.container is None:
557
                reply = self.client.get_account_info()
558
            elif self.path is None:
559
                reply = self.client.get_container_info(self.container)
560
            else:
561
                reply = self.client.get_object_info(self.path)
562
        except ClientError as err:
563
            raiseCLIError(err)
564
        print_dict(reply)
565

    
566
@command()
567
class store_meta(_store_container_command):
568
    """Get custom meta-content for account [, container [or object]]"""
569

    
570
    def update_parser(self, parser):
571
        parser.add_argument('-l', action='store_true', dest='detail', default=False,
572
            help='show detailed output')
573
        parser.add_argument('--until', action='store', dest='until', default=None,
574
            help='show metadata until that date')
575
        dateformat='%d/%m/%Y %H:%M:%S'
576
        parser.add_argument('--format', action='store', dest='format', default=dateformat,
577
            help='format to parse until date (default: "d/m/Y H:M:S")')
578
        parser.add_argument('--object_version', action='store', dest='object_version', default=None,
579
            help='show specific version \ (applies only for objects)')
580

    
581
    def getuntil(self, orelse=None):
582
        if hasattr(self.args, 'until'):
583
            import time
584
            until = getattr(self.args, 'until')
585
            if until is None:
586
                return None
587
            format = getattr(self.args, 'format')
588
            #except TypeError:
589
            try:
590
                t = time.strptime(until, format)
591
            except ValueError as err:
592
                raise CLIError(message='in --until: '+unicode(err), importance=1)
593
            return int(time.mktime(t))
594
        return orelse
595

    
596
    def main(self, container____path__ = None):
597
        super(self.__class__, self).main(container____path__)
598

    
599
        try:
600
            if self.container is None:
601
                print(bold(self.client.account))
602
                r = self.client.account_head(until=self.getuntil())
603
                reply = r.headers if getattr(self.args, 'detail') \
604
                    else pretty_keys(filter_in(r.headers, 'X-Account-Meta'), '-')
605
            elif self.path is None:
606
                print(bold(self.client.account+': '+self.container))
607
                r = self.client.container_head(until=self.getuntil())
608
                if getattr(self.args, 'detail'):
609
                    reply = r.headers
610
                else:
611
                    cmeta = 'container-meta'
612
                    ometa = 'object-meta'
613
                    reply = {cmeta:pretty_keys(filter_in(r.headers, 'X-Container-Meta'), '-'),
614
                        ometa:pretty_keys(filter_in(r.headers, 'X-Container-Object-Meta'), '-')}
615
            else:
616
                print(bold(self.client.account+': '+self.container+':'+self.path))
617
                r = self.client.object_head(self.path, version=getattr(self.args, 'object_version'))
618
                reply = r.headers if getattr(self.args, 'detail') \
619
                    else pretty_keys(filter_in(r.headers, 'X-Object-Meta'), '-')
620
        except ClientError as err:
621
            raiseCLIError(err)
622
        print_dict(reply)
623

    
624
@command()
625
class store_setmeta(_store_container_command):
626
    """Set a new metadatum for account [, container [or object]]"""
627

    
628
    def main(self, metakey, metavalue, container____path__=None):
629
        super(self.__class__, self).main(container____path__)
630
        try:
631
            if self.container is None:
632
                self.client.set_account_meta({metakey:metavalue})
633
            elif self.path is None:
634
                self.client.set_container_meta({metakey:metavalue})
635
            else:
636
                self.client.set_object_meta(self.path, {metakey:metavalue})
637
        except ClientError as err:
638
            raiseCLIError(err)
639

    
640
@command()
641
class store_delmeta(_store_container_command):
642
    """Delete an existing metadatum of account [, container [or object]]"""
643

    
644
    def main(self, metakey, container____path__=None):
645
        super(self.__class__, self).main(container____path__)
646
        try:
647
            if self.container is None:
648
                self.client.del_account_meta(metakey)
649
            elif self.path is None:
650
                self.client.del_container_meta(metakey)
651
            else:
652
                self.client.del_object_meta(metakey, self.path)
653
        except ClientError as err:
654
            raiseCLIError(err)
655

    
656
@command()
657
class store_quota(_store_account_command):
658
    """Get  quota for account [or container]"""
659

    
660
    def main(self, container = None):
661
        super(self.__class__, self).main()
662
        try:
663
            if container is None:
664
                reply = self.client.get_account_quota()
665
            else:
666
                reply = self.client.get_container_quota(container)
667
        except ClientError as err:
668
            raiseCLIError(err)
669
        print_dict(reply)
670

    
671
@command()
672
class store_setquota(_store_account_command):
673
    """Set new quota (in KB) for account [or container]"""
674

    
675
    def main(self, quota, container = None):
676
        super(self.__class__, self).main()
677
        try:
678
            if container is None:
679
                self.client.set_account_quota(quota)
680
            else:
681
                self.client.container = container
682
                self.client.set_container_quota(quota)
683
        except ClientError as err:
684
            raiseCLIError(err)
685

    
686
@command()
687
class store_versioning(_store_account_command):
688
    """Get  versioning for account [or container ]"""
689

    
690
    def main(self, container = None):
691
        super(self.__class__, self).main()
692
        try:
693
            if container is None:
694
                reply = self.client.get_account_versioning()
695
            else:
696
                reply = self.client.get_container_versioning(container)
697
        except ClientError as err:
698
            raiseCLIError(err)
699
        print_dict(reply)
700

    
701
@command()
702
class store_setversioning(_store_account_command):
703
    """Set new versioning (auto, none) for account [or container]"""
704

    
705
    def main(self, versioning, container = None):
706
        super(self.__class__, self).main()
707
        try:
708
            if container is None:
709
                self.client.set_account_versioning(versioning)
710
            else:
711
                self.client.container = container
712
                self.client.set_container_versioning(versioning)
713
        except ClientError as err:
714
            raiseCLIError(err)
715

    
716
@command()
717
class store_group(_store_account_command):
718
    """Get user groups details for account"""
719

    
720
    def main(self):
721
        super(self.__class__, self).main()
722
        try:
723
            reply = self.client.get_account_group()
724
        except ClientError as err:
725
            raiseCLIError(err)
726
        print_dict(reply)
727

    
728
@command()
729
class store_setgroup(_store_account_command):
730
    """Create/update a new user group on account"""
731

    
732
    def main(self, groupname, *users):
733
        super(self.__class__, self).main()
734
        try:
735
            self.client.set_account_group(groupname, users)
736
        except ClientError as err:
737
            raiseCLIError(err)
738

    
739
@command()
740
class store_delgroup(_store_account_command):
741
    """Delete a user group on an account"""
742

    
743
    def main(self, groupname):
744
        super(self.__class__, self).main()
745
        try:
746
            self.client.del_account_group(groupname)
747
        except ClientError as err:
748
            raiseCLIError(err)