1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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
34 from kamaki.cli import command#, set_api_description
35 from kamaki.clients.utils import filter_in
36 from kamaki.cli.errors import CLIError, raiseCLIError
37 from kamaki.cli.utils import format_size, print_dict, pretty_keys, print_list
38 from kamaki.cli.argument import FlagArgument, ValueArgument, IntArgument
39 #set_api_description('store', 'Pithos+ storage commands')
40 API_DESCRIPTION = {'store':'Pithos+ storage commands'}
41 from kamaki.clients.pithos import PithosClient, ClientError
42 from colors import bold
43 from sys import stdout, exit
45 from time import localtime, strftime, strptime, mktime
46 from datetime import datetime as dtm
48 from progress.bar import IncrementalBar
50 class DelimiterArgument(ValueArgument):
51 def __init__(self, caller_obj, help='', parsed_name=None, default=None):
52 super(DelimiterArgument, self).__init__(help, parsed_name, default)
53 self.caller_obj = caller_obj
57 if self.caller_obj.get_argument('recursive'):
59 return getattr(self, '_value', self.default)
61 def value(self, newvalue):
62 self._value = newvalue
64 class MetaArgument(ValueArgument):
67 if self._value is None:
70 for metastr in self._value.split('_'):
71 (key,val) = metastr.split(':')
75 class ProgressBarArgument(FlagArgument, IncrementalBar):
77 def __init__(self, help='', parsed_name='', default=True):
78 self.suffix = '%(percent)d%%'
79 super(ProgressBarArgument, self).__init__(help, parsed_name, default)
83 return getattr(self, '_value', self.default)
85 def value(self, newvalue):
86 """By default, it is on (True)"""
87 self._value = not newvalue
89 def get_generator(self, message):
92 msg = message.ljust(_message_len)
93 for i in self.iter(range(n)):
98 class SharingArgument(ValueArgument):
101 return getattr(self, '_value', self.default)
103 def value(self, newvalue):
106 permlist = newvalue.split(' ')
107 except AttributeError:
111 (key,val) = p.split('=')
113 raise CLIError(message='Error in --sharing', details='Incorrect format', importance=1)
114 if key.lower() not in ('read', 'write'):
115 raise CLIError(message='Error in --sharing', details='Invalid permition key %s'%key, importance=1)
116 val_list = val.split(',')
117 if not perms.has_key(key):
119 for item in val_list:
120 if item not in perms[key]:
121 perms[key].append(item)
124 class RangeArgument(ValueArgument):
127 return getattr(self, '_value', self.default)
129 def value(self, newvalue):
131 self._value = self.default
133 (start, end) = newvalue.split('_')
134 (start, end) = (int(start), int(end))
135 self._value = '%s-%s'%(start, end)
137 class DateArgument(ValueArgument):
138 DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
139 "%A, %d-%b-%y %H:%M:%S GMT",
140 "%a, %d %b %Y %H:%M:%S GMT"]
142 INPUT_FORMATS = DATE_FORMATS + ["%d-%m-%Y", "%H:%M:%S %d-%m-%Y"]
146 return getattr(self, '_value', self.default)
148 def value(self, newvalue):
151 self._value = self.format_date(newvalue)
153 def format_date(self, datestr):
154 for format in self.INPUT_FORMATS:
156 t = dtm.strptime(datestr, format)
159 self._value = t.strftime(self.DATE_FORMATS[0])
161 raise CLIError('Date Argument Error',
162 details='%s not a valid date. correct formats:\n\t%s'%(datestr, self.INPUT_FORMATS))
164 class _pithos_init(object):
165 def __init__(self, arguments={}):
166 self.arguments = arguments
168 self.config = self.get_argument('config')
172 def get_argument(self, arg_name):
173 return self.arguments[arg_name].value
176 self.token = self.config.get('store', 'token') or self.config.get('global', 'token')
177 self.base_url = self.config.get('store', 'url') or self.config.get('global', 'url')
178 self.account = self.config.get('store', 'account') or self.config.get('global', 'account')
179 self.container = self.config.get('store', 'container') or self.config.get('global',
181 self.client = PithosClient(base_url=self.base_url, token=self.token, account=self.account,
182 container=self.container)
184 class _store_account_command(_pithos_init):
185 """Base class for account level storage commands"""
187 def __init__(self, arguments={}):
188 super(_store_account_command, self).__init__(arguments)
189 self.arguments['account'] = ValueArgument('Specify the account', '--account')
191 def generator(self, message):
195 super(_store_account_command, self).main()
196 if self.arguments['account'].value is not None:
197 self.client.account = self.arguments['account'].value
199 class _store_container_command(_store_account_command):
200 """Base class for container level storage commands"""
202 def __init__(self, arguments={}):
203 super(_store_container_command, self).__init__(arguments)
204 self.arguments['container'] = ValueArgument('Specify the container name', '--container')
205 self.container = None
208 def extract_container_and_path(self, container_with_path, path_is_optional=True):
209 assert isinstance(container_with_path, str)
210 if ':' not in container_with_path:
211 if self.get_argument('container') is not None:
212 self.container = self.get_argument('container')
214 self.container = self.client.container
215 if self.container is None:
216 self.container = container_with_path
218 self.path = container_with_path
219 if not path_is_optional and self.path is None:
220 raise CLIError(message="Object path is missing", status=11)
222 cnp = container_with_path.split(':')
223 self.container = cnp[0]
230 raise CLIError(message="Object path is missing", status=11)
232 def main(self, container_with_path=None, path_is_optional=True):
233 super(_store_container_command, self).main()
234 if container_with_path is not None:
235 self.extract_container_and_path(container_with_path, path_is_optional)
236 self.client.container = self.container
237 elif self.get_argument('container') is not None:
238 self.client.container = self.get_argument('container')
239 self.container = self.client.container
243 class store_test(_store_container_command):
244 "Test stuff something""
247 super(self.__class__, self).main('pithos')
248 r = self.client.container_get()
249 print(unicode(r.content)+' '+unicode(r.json))
253 class store_list(_store_container_command):
254 """List containers, object trees or objects in a directory
257 def __init__(self, arguments = {}):
258 super(store_list, self).__init__(arguments)
259 self.arguments['detail'] = FlagArgument('show detailed output', '-l')
260 self.arguments['show_size'] = ValueArgument('print output in chunks of size N', '-N')
261 self.arguments['limit'] = IntArgument('show limited output', '-n')
262 self.arguments['marker'] = ValueArgument('show output greater that marker', '--marker')
263 self.arguments['prefix'] = ValueArgument('show output staritng with prefix', '--prefix')
264 self.arguments['delimiter'] = ValueArgument('show output up to delimiter', '--delimiter')
265 self.arguments['path'] = ValueArgument('show output starting with prefix up to /', '--path')
266 self.arguments['meta'] = ValueArgument('show output haviung the specified meta keys',
267 '---meta', default=[])
268 self.arguments['if_modified_since'] = ValueArgument('show output modified since then',
269 '--if-modified-since')
270 self.arguments['if_unmodified_since'] = ValueArgument('show output not modified since then',
271 '--if-unmodified-since')
272 self.arguments['until'] = DateArgument('show metadata until then', '--until')
273 self.arguments['format'] = ValueArgument('format to parse until data (default: d/m/Y H:M:S',
275 self.arguments['shared'] = FlagArgument('show only shared', '--shared')
276 self.arguments['public'] = FlagArgument('show only public', '--public')
278 def print_objects(self, object_list):
281 limit = self.get_argument('show_size')
283 except AttributeError:
286 for index,obj in enumerate(object_list):
287 if not obj.has_key('content_type'):
289 pretty_obj = obj.copy()
291 empty_space = ' '*(len(str(len(object_list))) - len(str(index)))
292 if obj['content_type'] == 'application/directory':
297 size = format_size(obj['bytes'])
298 pretty_obj['bytes'] = '%s (%s)'%(obj['bytes'],size)
299 oname = bold(obj['name'])
300 if self.get_argument('detail'):
301 print('%s%s. %s'%(empty_space, index, oname))
302 print_dict(pretty_keys(pretty_obj), exclude=('name'))
305 oname = '%s%s. %6s %s'%(empty_space, index, size, oname)
306 oname += '/' if isDir else ''
308 if limit <= index < len(object_list) and index%limit == 0:
309 print('(press "enter" to continue)')
312 def print_containers(self, container_list):
315 limit = self.get_argument('show_size')
317 except AttributeError:
319 for index,container in enumerate(container_list):
320 if container.has_key('bytes'):
321 size = format_size(container['bytes'])
322 cname = '%s. %s'%(index+1, bold(container['name']))
323 if self.get_argument('detail'):
325 pretty_c = container.copy()
326 if container.has_key('bytes'):
327 pretty_c['bytes'] = '%s (%s)'%(container['bytes'], size)
328 print_dict(pretty_keys(pretty_c), exclude=('name'))
331 if container.has_key('count') and container.has_key('bytes'):
332 print('%s (%s, %s objects)' % (cname, size, container['count']))
335 if limit <= index < len(container_list) and index%limit == 0:
336 print('(press "enter" to continue)')
339 def main(self, container____path__=None):
340 super(self.__class__, self).main(container____path__)
342 if self.container is None:
343 r = self.client.account_get(limit=self.get_argument('limit'),
344 marker=self.get_argument('marker'),
345 if_modified_since=self.get_argument('if_modified_since'),
346 if_unmodified_since=self.get_argument('if_unmodified_since'),
347 until=self.get_argument('until'),
348 show_only_shared=self.get_argument('shared'))
349 self.print_containers(r.json)
351 r = self.client.container_get(limit=self.get_argument('limit'),
352 marker=self.get_argument('marker'), prefix=self.get_argument('prefix'),
353 delimiter=self.get_argument('delimiter'), path=self.get_argument('path'),
354 if_modified_since=self.get_argument('if_modified_since'),
355 if_unmodified_since=self.get_argument('if_unmodified_since'),
356 until=self.get_argument('until'),
357 meta=self.get_argument('meta'), show_only_shared=self.get_argument('shared'))
358 self.print_objects(r.json)
359 except ClientError as err:
363 class store_mkdir(_store_container_command):
364 """Create a directory"""
366 def main(self, container___directory):
367 super(self.__class__, self).main(container___directory, path_is_optional=False)
369 self.client.create_directory(self.path)
370 except ClientError as err:
374 class store_create(_store_container_command):
375 """Create a container or a directory object"""
378 def __init__(self, arguments={}):
379 super(self.__class__, self).__init__(arguments)
380 self.arguments['versioning'] = ValueArgument('set container versioning (auto/none)',
382 self.arguments['quota'] = IntArgument('set default container quota', '--quota')
383 self.arguments['meta'] = MetaArgument('set container metadata', '---meta')
385 def main(self, container____directory__):
386 super(self.__class__, self).main(container____directory__)
388 if self.path is None:
389 self.client.container_put(quota=self.get_argument('quota'),
390 versioning=self.get_argument('versioning'),
391 metadata=self.get_argument('metadata'))
393 self.client.create_directory(self.path)
394 except ClientError as err:
398 class store_copy(_store_container_command):
401 def __init__(self, arguments={}):
402 super(self.__class__, self).__init__(arguments)
403 self.arguments['source_version']=ValueArgument('copy specific version', '--source-version')
404 self.arguments['public']=ValueArgument('make object publicly accessible', '--public')
405 self.arguments['content_type']=ValueArgument('change object\'s content type',
407 self.arguments['delimiter']=DelimiterArgument(self, parsed_name='--delimiter',
408 help = u'mass copy objects with path staring with src_object + delimiter')
409 self.arguments['recursive']=FlagArgument('mass copy with delimiter /', ('-r','--recursive'))
411 def main(self, source_container___path, destination_container____path__):
412 super(self.__class__, self).main(source_container___path, path_is_optional=False)
414 dst = destination_container____path__.split(':')
416 dst_path = dst[1] if len(dst) > 1 else False
417 self.client.copy_object(src_container = self.container, src_object = self.path,
418 dst_container = dst_cont, dst_object = dst_path,
419 source_version=self.get_argument('source_version'),
420 public=self.get_argument('public'),
421 content_type=self.get_argument('content_type'),
422 delimiter=self.get_argument('delimiter'))
423 except ClientError as err:
427 class store_move(_store_container_command):
430 def __init__(self, arguments={}):
431 super(self.__class__, self).__init__(arguments)
433 self.arguments['source_version']=ValueArgument('copy specific version', '--source-version')
434 self.arguments['public']=FlagArgument('make object publicly accessible', '--public')
435 self.arguments['content_type']=ValueArgument('change object\'s content type',
437 self.arguments['delimiter']=DelimiterArgument(self, parsed_name='--delimiter',
438 help = u'mass copy objects with path staring with src_object + delimiter')
439 self.arguments['recursive']=FlagArgument('mass copy with delimiter /', ('-r','--recursive'))
441 def main(self, source_container___path, destination_container____path__):
442 super(self.__class__, self).main(source_container___path, path_is_optional=False)
444 dst = destination_container____path__.split(':')
446 dst_path = dst[1] if len(dst) > 1 else False
447 self.client.move_object(src_container = self.container, src_object = self.path,
448 dst_container = dst_cont, dst_object = dst_path,
449 source_version=self.get_argument('source_version'),
450 public=self.get_argument('public'),
451 content_type=self.get_argument('content_type'),
452 delimiter=self.get_argument('delimiter'))
453 except ClientError as err:
457 class store_append(_store_container_command):
458 """Append local file to (existing) remote object"""
460 def __init__(self, arguments={}):
461 super(self.__class__, self).__init__(arguments)
462 self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
465 def main(self, local_path, container___path):
466 super(self.__class__, self).main(container___path, path_is_optional=False)
468 f = open(local_path, 'r')
469 upload_cb = self.arguments['progress_bar'].get_generator('Appending blocks')
470 self.client.append_object(object=self.path, source_file = f, upload_cb = upload_cb)
471 except ClientError as err:
475 class store_truncate(_store_container_command):
476 """Truncate remote file up to a size"""
478 def main(self, container___path, size=0):
479 super(self.__class__, self).main(container___path, path_is_optional=False)
481 self.client.truncate_object(self.path, size)
482 except ClientError as err:
486 class store_overwrite(_store_container_command):
487 """Overwrite part (from start to end) of a remote file"""
489 def __init__(self, arguments={}):
490 super(self.__class__, self).__init__(arguments)
491 self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
494 def main(self, local_path, container___path, start, end):
495 super(self.__class__, self).main(container___path, path_is_optional=False)
497 f = open(local_path, 'r')
498 upload_cb = self.arguments['progress_bar'].get_generator('Overwritting blocks')
499 self.client.overwrite_object(object=self.path, start=start, end=end,
500 source_file=f, upload_cb = upload_cb)
501 except ClientError as err:
505 class store_manifest(_store_container_command):
506 """Create a remote file with uploaded parts by manifestation"""
508 def __init__(self, arguments={}):
509 super(self.__class__, self).__init__(arguments)
510 self.arguments['etag'] = ValueArgument('check written data', '--etag')
511 self.arguments['content_encoding']=ValueArgument('provide the object MIME content type',
512 '--content-encoding')
513 self.arguments['content_disposition'] = ValueArgument('provide the presentation style of the object',
514 '--content-disposition')
515 self.arguments['content_type']=ValueArgument('create object with specific content type',
517 self.arguments['sharing']=SharingArgument(parsed_name='--sharing',
518 help='define sharing object policy ( "read=user1,grp1,user2,... write=user1,grp2,...')
519 self.arguments['public']=FlagArgument('make object publicly accessible', '--public')
521 def main(self, container___path):
522 super(self.__class__, self).main(container___path, path_is_optional=False)
524 self.client.create_object_by_manifestation(self.path,
525 content_encoding=self.get_argument('content_encoding'),
526 content_disposition=self.get_argument('content_disposition'),
527 content_type=self.get_argument('content_type'),
528 sharing=self.get_argument('sharing'), public=self.get_argument('public'))
529 except ClientError as err:
533 class store_upload(_store_container_command):
536 def __init__(self, arguments={}):
537 super(self.__class__, self).__init__(arguments)
538 self.arguments['use_hashes'] = FlagArgument('provide hashmap file instead of data',
540 self.arguments['etag'] = ValueArgument('check written data', '--etag')
541 self.arguments['unchunked'] = FlagArgument('avoid chunked transfer mode', '--unchunked')
542 self.arguments['content_encoding']=ValueArgument('provide the object MIME content type',
543 '--content-encoding')
544 self.arguments['content_disposition'] = ValueArgument('provide the presentation style of the object',
545 '--content-disposition')
546 self.arguments['content_type']=ValueArgument('create object with specific content type',
548 self.arguments['sharing']=SharingArgument(parsed_name='--sharing',
549 help='define sharing object policy ( "read=user1,grp1,user2,... write=user1,grp2,...')
550 self.arguments['public']=FlagArgument('make object publicly accessible', '--public')
551 self.arguments['poolsize']=IntArgument('set pool size', '--with-pool-size')
552 self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
555 def main(self, local_path, container____path__):
556 super(self.__class__, self).main(container____path__)
557 remote_path = local_path if self.path is None else self.path
558 poolsize = self.get_argument('poolsize')
559 if poolsize is not None:
560 self.POOL_SIZE = poolsize
562 with open(local_path) as f:
563 if self.get_argument('unchunked'):
564 self.client.upload_object_unchunked(remote_path, f,
565 etag=self.get_argument('etag'), withHashFile=self.get_argument('use_hashes'),
566 content_encoding=self.get_argument('content_encoding'),
567 content_disposition=self.get_argument('content_disposition'),
568 content_type=self.get_argument('content_type'),
569 sharing=self.get_argument('sharing'), public=self.get_argument('public'))
571 hash_cb=self.arguments['progress_bar'].get_generator('Calculating block hashes')
572 upload_cb=self.arguments['progress_bar'].get_generator('Uploading')
573 self.client.upload_object(remote_path, f, hash_cb=hash_cb, upload_cb=upload_cb,
574 content_encoding=self.get_argument('content_encoding'),
575 content_disposition=self.get_argument('content_disposition'),
576 content_type=self.get_argument('content_type'),
577 sharing=self.get_argument('sharing'), public=self.get_argument('public'))
578 except ClientError as err:
580 print 'Upload completed'
583 class store_download(_store_container_command):
584 """Download a file"""
586 def __init__(self, arguments={}):
587 super(self.__class__, self).__init__(arguments)
588 self.arguments['resume'] = FlagArgument(parsed_name='--resume',
589 help = 'Resume a previous download instead of overwritting it')
590 self.arguments['range'] = RangeArgument('show range of data', '--range')
591 self.arguments['if_match'] = ValueArgument('show output if ETags match', '--if-match')
592 self.arguments['if_none_match'] = ValueArgument('show output if ETags match',
594 self.arguments['if_modified_since'] = DateArgument('show output modified since then',
595 '--if-modified-since')
596 self.arguments['if_unmodified_since'] = DateArgument('show output unmodified since then',
597 '--if-unmodified-since')
598 self.arguments['object_version'] = ValueArgument('get the specific version',
600 self.arguments['poolsize']=IntArgument('set pool size', '--with-pool-size')
601 self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
604 def main(self, container___path, local_path=None):
605 super(self.__class__, self).main(container___path, path_is_optional=False)
609 if local_path is None:
613 if self.get_argument('resume'):
614 out=open(local_path, 'rwb+')
616 out=open(local_path, 'wb+')
617 except IOError as err:
618 raise CLIError(message='Cannot write to file %s - %s'%(local_path,unicode(err)),
620 download_cb = self.arguments['progress_bar'].get_generator('Downloading')
621 poolsize = self.get_argument('poolsize')
622 if poolsize is not None:
623 self.POOL_SIZE = int(poolsize)
626 self.client.download_object(self.path, out, download_cb,
627 range=self.get_argument('range'), version=self.get_argument('object_version'),
628 if_match=self.get_argument('if_match'), resume=self.get_argument('resume'),
629 if_none_match=self.get_argument('if_none_match'),
630 if_modified_since=self.get_argument('if_modified_since'),
631 if_unmodified_since=self.get_argument('if_unmodified_since'))
632 except ClientError as err:
634 except KeyboardInterrupt:
635 print('\ndownload canceled by user')
636 if local_path is not None:
637 print('to resume, re-run with --resume')
641 class store_hashmap(_store_container_command):
642 """Get the hashmap of an object"""
644 def __init__(self, arguments={}):
645 super(self.__class__, self).__init__(arguments)
646 self.arguments['if_match'] = ValueArgument('show output if ETags match', '--if-match')
647 self.arguments['if_none_match'] = ValueArgument('show output if ETags match',
649 self.arguments['if_modified_since'] = DateArgument('show output modified since then',
650 '--if-modified-since')
651 self.arguments['if_unmodified_since'] = DateArgument('show output unmodified since then',
652 '--if-unmodified-since')
653 self.arguments['object_version'] = ValueArgument('get the specific version',
656 def main(self, container___path):
657 super(self.__class__, self).main(container___path, path_is_optional=False)
659 data = self.client.get_object_hashmap(self.path,
660 version=self.arguments('object_version'),
661 if_match=self.arguments('if_match'),
662 if_none_match=self.arguments('if_none_match'),
663 if_modified_since=self.arguments('if_modified_since'),
664 if_unmodified_since=self.arguments('if_unmodified_since'))
665 except ClientError as err:
670 class store_delete(_store_container_command):
671 """Delete a container [or an object]"""
673 def __init__(self, arguments={}):
674 super(self.__class__, self).__init__(arguments)
675 self.arguments['until'] = DateArgument('remove history until that date', '--until')
676 self.arguments['recursive'] = FlagArgument('empty dir or container and delete (if dir)',
678 self.arguments['delimiter'] = DelimiterArgument(self, parsed_name='--delimiter',
679 help = 'mass delete objects with path staring with <object><delimiter>')
681 def main(self, container____path__):
682 super(self.__class__, self).main(container____path__)
684 if self.path is None:
685 self.client.del_container(until=self.get_argument('until'),
686 delimiter=self.get_argument('delimiter'))
688 #self.client.delete_object(self.path)
689 self.client.del_object(self.path, until=self.get_argument('until'),
690 delimiter=self.get_argument('delimiter'))
691 except ClientError as err:
695 class store_purge(_store_container_command):
696 """Purge a container"""
698 def main(self, container):
699 super(self.__class__, self).main(container)
701 self.client.purge_container()
702 except ClientError as err:
706 class store_publish(_store_container_command):
707 """Publish an object"""
709 def main(self, container___path):
710 super(self.__class__, self).main(container___path, path_is_optional=False)
712 self.client.publish_object(self.path)
713 except ClientError as err:
717 class store_unpublish(_store_container_command):
718 """Unpublish an object"""
720 def main(self, container___path):
721 super(self.__class__, self).main(container___path, path_is_optional=False)
723 self.client.unpublish_object(self.path)
724 except ClientError as err:
728 class store_permitions(_store_container_command):
729 """Get object read/write permitions"""
731 def main(self, container___path):
732 super(self.__class__, self).main(container___path, path_is_optional=False)
734 reply = self.client.get_object_sharing(self.path)
736 except ClientError as err:
740 class store_setpermitions(_store_container_command):
741 """Set sharing permitions"""
743 def format_permition_dict(self,permitions):
746 for perms in permitions:
747 splstr = perms.split('=')
748 if 'read' == splstr[0]:
749 read = [user_or_group.strip() \
750 for user_or_group in splstr[1].split(',')]
751 elif 'write' == splstr[0]:
752 write = [user_or_group.strip() \
753 for user_or_group in splstr[1].split(',')]
757 if not read and not write:
758 raise CLIError(message='Usage:\tread=<groups,users> write=<groups,users>',
762 def main(self, container___path, *permitions):
763 super(self.__class__, self).main(container___path, path_is_optional=False)
764 (read, write) = self.format_permition_dict(permitions)
766 self.client.set_object_sharing(self.path,
767 read_permition=read, write_permition=write)
768 except ClientError as err:
772 class store_delpermitions(_store_container_command):
773 """Delete all sharing permitions"""
775 def main(self, container___path):
776 super(self.__class__, self).main(container___path, path_is_optional=False)
778 self.client.del_object_sharing(self.path)
779 except ClientError as err:
783 class store_info(_store_container_command):
784 """Get information for account [, container [or object]]"""
787 def main(self, container____path__=None):
788 super(self.__class__, self).main(container____path__)
790 if self.container is None:
791 reply = self.client.get_account_info()
792 elif self.path is None:
793 reply = self.client.get_container_info(self.container)
795 reply = self.client.get_object_info(self.path)
796 except ClientError as err:
801 class store_meta(_store_container_command):
802 """Get custom meta-content for account [, container [or object]]"""
804 def __init__(self, arguments = {}):
805 super(self.__class__, self).__init__(arguments)
806 self.arguments['detail'] = FlagArgument('show detailed output', '-l')
807 self.arguments['until'] = DateArgument('show metadata until then', '--until')
808 self.arguments['object_version'] = ValueArgument(parsed_name='--object-version',
809 help='show specific version \ (applies only for objects)')
811 def main(self, container____path__ = None):
812 super(self.__class__, self).main(container____path__)
814 detail = self.get_argument('detail')
816 if self.container is None:
817 print(bold(self.client.account))
819 reply = self.client.get_account_info(until=self.get_argument('until'))
821 reply = self.client.get_account_meta(until=self.get_argument('until'))
822 reply = pretty_keys(reply, '-')
823 elif self.path is None:
824 print(bold(self.client.account+': '+self.container))
826 reply = self.client.get_container_info(until = self.get_argument('until'))
828 cmeta = self.client.get_container_meta(until=self.get_argument('until'))
829 ometa = self.client.get_container_object_meta(until=self.get_argument('until'))
830 reply = {'container-meta':pretty_keys(cmeta, '-'),
831 'object-meta':pretty_keys(ometa, '-')}
833 print(bold(self.client.account+': '+self.container+':'+self.path))
834 version=self.get_argument('object_version')
836 reply = self.client.get_object_info(self.path, version = version)
838 reply = self.client.get_object_meta(self.path, version=version)
839 reply = pretty_keys(pretty_keys(reply, '-'))
840 except ClientError as err:
845 class store_setmeta(_store_container_command):
846 """Set a new metadatum for account [, container [or object]]"""
848 def main(self, metakey___metaval, container____path__=None):
849 super(self.__class__, self).main(container____path__)
851 metakey, metavalue = metakey___metaval.split(':')
853 raise CLIError(message='Meta variables should be formated as metakey:metavalue',
856 if self.container is None:
857 self.client.set_account_meta({metakey:metavalue})
858 elif self.path is None:
859 self.client.set_container_meta({metakey:metavalue})
861 self.client.set_object_meta(self.path, {metakey:metavalue})
862 except ClientError as err:
866 class store_delmeta(_store_container_command):
867 """Delete an existing metadatum of account [, container [or object]]"""
869 def main(self, metakey, container____path__=None):
870 super(self.__class__, self).main(container____path__)
872 if self.container is None:
873 self.client.del_account_meta(metakey)
874 elif self.path is None:
875 self.client.del_container_meta(metakey)
877 self.client.del_object_meta(metakey, self.path)
878 except ClientError as err:
882 class store_quota(_store_account_command):
883 """Get quota for account [or container]"""
885 def main(self, container = None):
886 super(self.__class__, self).main()
888 if container is None:
889 reply = self.client.get_account_quota()
891 reply = self.client.get_container_quota(container)
892 except ClientError as err:
897 class store_setquota(_store_account_command):
898 """Set new quota (in KB) for account [or container]"""
900 def main(self, quota, container = None):
901 super(self.__class__, self).main()
903 if container is None:
904 self.client.set_account_quota(quota)
906 self.client.container = container
907 self.client.set_container_quota(quota)
908 except ClientError as err:
912 class store_versioning(_store_account_command):
913 """Get versioning for account [or container ]"""
915 def main(self, container = None):
916 super(self.__class__, self).main()
918 if container is None:
919 reply = self.client.get_account_versioning()
921 reply = self.client.get_container_versioning(container)
922 except ClientError as err:
927 class store_setversioning(_store_account_command):
928 """Set new versioning (auto, none) for account [or container]"""
930 def main(self, versioning, container = None):
931 super(self.__class__, self).main()
933 if container is None:
934 self.client.set_account_versioning(versioning)
936 self.client.container = container
937 self.client.set_container_versioning(versioning)
938 except ClientError as err:
942 class store_group(_store_account_command):
943 """Get user groups details for account"""
946 super(self.__class__, self).main()
948 reply = self.client.get_account_group()
949 except ClientError as err:
954 class store_setgroup(_store_account_command):
955 """Create/update a new user group on account"""
957 def main(self, groupname, *users):
958 super(self.__class__, self).main()
960 self.client.set_account_group(groupname, users)
961 except ClientError as err:
965 class store_delgroup(_store_account_command):
966 """Delete a user group on an account"""
968 def main(self, groupname):
969 super(self.__class__, self).main()
971 self.client.del_account_group(groupname)
972 except ClientError as err:
976 class store_sharers(_store_account_command):
977 """List the accounts that share objects with default account"""
979 def __init__(self, arguments = {}):
980 super(self.__class__, self).__init__(arguments)
981 self.arguments['detail'] = FlagArgument('show detailed output', '-l')
982 self.arguments['limit'] = IntArgument('show limited output', '--n', default=1000)
983 self.arguments['marker'] = ValueArgument('show output greater then marker', '--marker')
986 super(self.__class__, self).main()
988 accounts = self.client.get_sharing_accounts(marker=self.get_argument('marker'))
989 except ClientError as err:
993 stdout.write(bold(acc['name'])+' ')
994 if self.get_argument('detail'):
995 print_dict(acc, exclude='name', ident=18)
996 if not self.get_argument('detail'):
1000 class store_versions(_store_container_command):
1001 """Get the version list of an object"""
1003 def main(self, container___path):
1004 super(store_versions, self).main(container___path)
1006 versions = self.client.get_object_versionlist(self.path)
1007 except ClientError as err:
1010 print('%s:%s versions'%(self.container,self.path))
1011 for vitem in versions:
1012 t = localtime(float(vitem[1]))
1013 vid = bold(unicode(vitem[0]))
1014 print('\t%s \t(%s)'%(vid, strftime('%d-%m-%Y %H:%M:%S', t)))