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
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
47 from progress.bar import IncrementalBar
49 class DelimiterArgument(ValueArgument):
50 def __init__(self, caller_obj, help='', parsed_name=None, default=None):
51 super(DelimiterArgument, self).__init__(help, parsed_name, default)
52 self.caller_obj = caller_obj
56 if self.caller_obj.get_argument('recursive'):
58 return getattr(self, '_value', self.default)
60 def value(self, newvalue):
61 self._value = newvalue
63 class UntilArgument(ValueArgument):
64 def __init__(self, caller_obj, help='', parsed_name=None, default=None):
65 super(UntilArgument, self).__init__(help, parsed_name, default)
66 self.caller_obj = caller_obj
70 _value = getattr(self, '_value', self.default)
73 format = self.caller_obj.get_argument('format')
75 t = strptime(_value, format)
76 except ValueError as err:
77 raise CLIError(message='in --until: '+unicode(err), importance=1)
80 def value(self, newvalue):
81 self._value = newvalue
83 class MetaArgument(ValueArgument):
86 if self._value is None:
89 for metastr in self._value.split('_'):
90 (key,val) = metastr.split(':')
94 class ProgressBarArgument(FlagArgument, IncrementalBar):
96 def __init__(self, help='', parsed_name='', default=True):
97 self.suffix = '%(percent)d%%'
98 super(ProgressBarArgument, self).__init__(help, parsed_name, default)
102 return getattr(self, '_value', self.default)
104 def value(self, newvalue):
105 """By default, it is on (True)"""
106 self._value = not newvalue
108 def get_generator(self, message):
111 msg = message.ljust(_message_len)
112 for i in self.iter(range(n)):
117 class _pithos_init(object):
118 def __init__(self, arguments={}):
119 self.arguments = arguments
121 self.config = self.get_argument('config')
125 def get_argument(self, arg_name):
126 return self.arguments[arg_name].value
129 self.token = self.config.get('store', 'token') or self.config.get('global', 'token')
130 self.base_url = self.config.get('store', 'url') or self.config.get('global', 'url')
131 self.account = self.config.get('store', 'account') or self.config.get('global', 'account')
132 self.container = self.config.get('store', 'container') or self.config.get('global',
134 self.client = PithosClient(base_url=self.base_url, token=self.token, account=self.account,
135 container=self.container)
137 class _store_account_command(_pithos_init):
138 """Base class for account level storage commands"""
140 def __init__(self, arguments={}):
141 super(_store_account_command, self).__init__(arguments)
142 self.arguments['account'] = ValueArgument('Specify the account', '--account')
144 def generator(self, message):
148 super(_store_account_command, self).main()
149 if self.arguments['account'].value is not None:
150 self.client.account = self.arguments['account'].value
152 class _store_container_command(_store_account_command):
153 """Base class for container level storage commands"""
155 def __init__(self, arguments={}):
156 super(_store_container_command, self).__init__(arguments)
157 self.arguments['container'] = ValueArgument('Specify the container name', '--container')
158 self.container = None
161 def extract_container_and_path(self, container_with_path, path_is_optional=True):
162 assert isinstance(container_with_path, str)
163 if ':' not in container_with_path:
164 if self.get_argument('container') is not None:
165 self.container = self.get_argument('container')
167 self.container = self.client.container
168 if self.container is None:
169 self.container = container_with_path
171 self.path = container_with_path
172 if not path_is_optional and self.path is None:
173 raise CLIError(message="Object path is missing", status=11)
175 cnp = container_with_path.split(':')
176 self.container = cnp[0]
183 raise CLIError(message="Object path is missing", status=11)
185 def main(self, container_with_path=None, path_is_optional=True):
186 super(_store_container_command, self).main()
187 if container_with_path is not None:
188 self.extract_container_and_path(container_with_path, path_is_optional)
189 self.client.container = self.container
190 elif self.get_argument('container') is not None:
191 self.client.container = self.get_argument('container')
192 self.container = self.client.container
196 class store_test(_store_container_command):
197 "Test stuff something""
200 super(self.__class__, self).main('pithos')
201 r = self.client.container_get()
202 print(unicode(r.content)+' '+unicode(r.json))
206 class store_list(_store_container_command):
207 """List containers, object trees or objects in a directory
210 def __init__(self, arguments = {}):
211 super(store_list, self).__init__(arguments)
212 self.arguments['detail'] = FlagArgument('show detailed output', '-l')
213 self.arguments['show_size'] = ValueArgument('print output in chunks of size N', '-N')
214 self.arguments['limit'] = ValueArgument('show limited output', '-n')
215 self.arguments['marker'] = ValueArgument('show output greater that marker', '--marker')
216 self.arguments['prefix'] = ValueArgument('show output staritng with prefix', '--prefix')
217 self.arguments['delimiter'] = ValueArgument('show output up to delimiter', '--delimiter')
218 self.arguments['path'] = ValueArgument('show output starting with prefix up to /', '--path')
219 self.arguments['meta'] = ValueArgument('show output haviung the specified meta keys',
220 '---meta', default=[])
221 self.arguments['if_modified_since'] = ValueArgument('show output modified since then',
222 '--if-modified-since')
223 self.arguments['if_unmodified_since'] = ValueArgument('show output not modified since then',
224 '--if-unmodified-since')
225 self.arguments['until'] = UntilArgument(self, 'show metadata until then', '--until')
226 self.arguments['format'] = ValueArgument('format to parse until data (default: d/m/Y H:M:S',
228 self.arguments['shared'] = FlagArgument('show only shared', '--shared')
229 self.arguments['public'] = FlagArgument('show only public', '--public')
231 def print_objects(self, object_list):
234 limit = self.get_argument('show_size')
236 except AttributeError:
239 for index,obj in enumerate(object_list):
240 if not obj.has_key('content_type'):
242 pretty_obj = obj.copy()
244 empty_space = ' '*(len(str(len(object_list))) - len(str(index)))
245 if obj['content_type'] == 'application/directory':
250 size = format_size(obj['bytes'])
251 pretty_obj['bytes'] = '%s (%s)'%(obj['bytes'],size)
252 oname = bold(obj['name'])
253 if self.get_argument('detail'):
254 print('%s%s. %s'%(empty_space, index, oname))
255 print_dict(pretty_keys(pretty_obj), exclude=('name'))
258 oname = '%s%s. %6s %s'%(empty_space, index, size, oname)
259 oname += '/' if isDir else ''
261 if limit <= index < len(object_list) and index%limit == 0:
262 print('(press "enter" to continue)')
265 def print_containers(self, container_list):
268 limit = self.get_argument('show_size')
270 except AttributeError:
272 for index,container in enumerate(container_list):
273 if container.has_key('bytes'):
274 size = format_size(container['bytes'])
275 cname = '%s. %s'%(index+1, bold(container['name']))
276 if self.get_argument('detail'):
278 pretty_c = container.copy()
279 if container.has_key('bytes'):
280 pretty_c['bytes'] = '%s (%s)'%(container['bytes'], size)
281 print_dict(pretty_keys(pretty_c), exclude=('name'))
284 if container.has_key('count') and container.has_key('bytes'):
285 print('%s (%s, %s objects)' % (cname, size, container['count']))
288 if limit <= index < len(container_list) and index%limit == 0:
289 print('(press "enter" to continue)')
292 def main(self, container____path__=None):
293 super(self.__class__, self).main(container____path__)
295 if self.container is None:
296 r = self.client.account_get(limit=self.get_argument('limit'),
297 marker=self.get_argument('marker'),
298 if_modified_since=self.get_argument('if_modified_since'),
299 if_unmodified_since=self.get_argument('if_unmodified_since'),
300 until=self.get_argument('until'),
301 show_only_shared=self.get_argument('shared'))
302 self.print_containers(r.json)
304 r = self.client.container_get(limit=self.get_argument('limit'),
305 marker=self.get_argument('marker'), prefix=self.get_argument('prefix'),
306 delimiter=self.get_argument('delimiter'), path=self.get_argument('path'),
307 if_modified_since=self.get_argument('if_modified_since'),
308 if_unmodified_since=self.get_argument('if_unmodified_since'),
309 until=self.get_argument('until'),
310 meta=self.get_argument('meta'), show_only_shared=self.get_argument('shared'))
311 self.print_objects(r.json)
312 except ClientError as err:
316 class store_mkdir(_store_container_command):
317 """Create a directory"""
319 def main(self, container___directory):
320 super(self.__class__, self).main(container___directory, path_is_optional=False)
322 self.client.create_directory(self.path)
323 except ClientError as err:
327 class store_create(_store_container_command):
328 """Create a container or a directory object"""
331 def __init__(self, arguments={}):
332 super(self.__class__, self).__init__(arguments)
333 self.arguments['versioning'] = ValueArgument('set container versioning (auto/none)',
335 self.arguments['quota'] = ValueArgument('set default container quota', '--quota')
336 self.arguments['meta'] = MetaArgument('set container metadata', '---meta')
338 def main(self, container____directory__):
339 super(self.__class__, self).main(container____directory__)
341 if self.path is None:
342 self.client.container_put(quota=self.get_argument('quota'),
343 versioning=self.get_argument('versioning'),
344 metadata=self.get_argument('metadata'))
346 self.client.create_directory(self.path)
347 except ClientError as err:
351 class store_copy(_store_container_command):
354 def __init__(self, arguments={}):
355 super(self.__class__, self).__init__(arguments)
356 self.arguments['source_version']=ValueArgument('copy specific version', '--source-version')
357 self.arguments['public']=ValueArgument('make object publicly accessible', '--public')
358 self.arguments['content_type']=ValueArgument('change object\'s content type',
360 self.arguments['delimiter']=DelimiterArgument(self, parsed_name='--delimiter',
361 help = u'mass copy objects with path staring with src_object + delimiter')
362 self.arguments['recursive']=FlagArgument('mass copy with delimiter /', ('-r','--recursive'))
364 def main(self, source_container___path, destination_container____path__):
365 super(self.__class__, self).main(source_container___path, path_is_optional=False)
367 dst = destination_container____path__.split(':')
369 dst_path = dst[1] if len(dst) > 1 else False
370 self.client.copy_object(src_container = self.container, src_object = self.path,
371 dst_container = dst_cont, dst_object = dst_path,
372 source_version=self.get_argument('source_version'),
373 public=self.get_argument('public'),
374 content_type=self.get_argument('content_type'),
375 delimiter=self.get_argument('delimiter'))
376 except ClientError as err:
380 class store_move(_store_container_command):
383 def __init__(self, arguments={}):
384 super(self.__class__, self).__init(arguments)
386 self.arguments['source_version']=ValueArgument('copy specific version', '--source-version')
387 self.arguments['public']=ValueArgument('make object publicly accessible', '--public')
388 self.arguments['content_type']=ValueArgument('change object\'s content type',
390 self.arguments['delimiter']=DelimiterArgument(self, parsed_name='--delimiter',
391 help = u'mass copy objects with path staring with src_object + delimiter')
392 self.arguments['recursive']=FlagArgument('mass copy with delimiter /', ('-r','--recursive'))
394 def main(self, source_container___path, destination_container____path__):
395 super(self.__class__, self).main(source_container___path, path_is_optional=False)
397 dst = destination_container____path__.split(':')
399 dst_path = dst[1] if len(dst) > 1 else False
400 self.client.move_object(src_container = self.container, src_object = self.path,
401 dst_container = dst_cont, dst_object = dst_path,
402 source_version=self.get_argument('source_version'),
403 public=self.get_argument('public'),
404 content_type=self.get_argument('content_type'),
405 delimiter=self.get_argument('delimiter'))
406 except ClientError as err:
410 class store_append(_store_container_command):
411 """Append local file to (existing) remote object"""
413 def __init__(self, arguments={}):
414 super(self.__class__, self).__init__(arguments)
415 self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
418 def main(self, local_path, container___path):
419 super(self.__class__, self).main(container___path, path_is_optional=False)
421 f = open(local_path, 'r')
422 upload_cb = self.arguments['progress_bar'].get_generator('Appending blocks')
423 self.client.append_object(object=self.path, source_file = f, upload_cb = upload_cb)
424 except ClientError as err:
428 class store_truncate(_store_container_command):
429 """Truncate remote file up to a size"""
431 def main(self, container___path, size=0):
432 super(self.__class__, self).main(container___path, path_is_optional=False)
434 self.client.truncate_object(self.path, size)
435 except ClientError as err:
439 class store_overwrite(_store_container_command):
440 """Overwrite part (from start to end) of a remote file"""
442 def __init__(self, arguments={}):
443 super(self.__class__, self).__init__(arguments)
444 self.arguments['progress_bar'] = ProgressBarArgument('do not show progress bar',
447 def main(self, local_path, container___path, start, end):
448 super(self.__class__, self).main(container___path, path_is_optional=False)
450 f = open(local_path, 'r')
451 upload_cb = self.arguments['progress_bar'].get_generator('Overwritting blocks')
452 self.client.overwrite_object(object=self.path, start=start, end=end,
453 source_file=f, upload_cb = upload_cb)
454 except ClientError as err:
457 class SharingArgument(ValueArgument):
460 return getattr(self, '_value', self.default)
462 def value(self, newvalue):
465 permlist = newvalue.split(' ')
466 except AttributeError:
470 (key,val) = p.split('=')
472 raise CLIError(message='Error in --sharing', details='Incorrect format', importance=1)
473 if key.lower() not in ('read', 'write'):
474 raise CLIError(message='Error in --sharing', details='Invalid permition key %s'%key, importance=1)
475 val_list = val.split(',')
476 if not perms.has_key(key):
478 for item in val_list:
479 if item not in perms[key]:
480 perms[key].append(item)
484 class store_manifest(_store_container_command):
485 """Create a remote file with uploaded parts by manifestation"""
487 def __init__(self, arguments={}):
488 super(self.__class__, self).__init__(arguments)
489 self.arguments['etag'] = ValueArgument('check written data', '--etag')
490 self.arguments['content_encoding']=ValueArgument('provide the object MIME content type',
491 '--content-encoding')
492 self.arguments['content_disposition'] = ValueArgument('provide the presentation style of the object',
493 '--content-disposition')
494 self.arguments['content_type']=ValueArgument('create object with specific content type',
496 self.arguments['sharing']=SharingArgument(parsed_name='--sharing',
497 help='define sharing object policy ( "read=user1,grp1,user2,... write=user1,grp2,...')
498 self.arguments['public']=ValueArgument('make object publicly accessible', '--public')
500 def main(self, container___path):
501 super(self.__class__, self).main(container___path, path_is_optional=False)
503 self.client.create_object_by_manifestation(self.path,
504 content_encoding=self.get_argument('content_encoding'),
505 content_disposition=self.get_argument('content_disposition'),
506 content_type=self.get_argument('content_type'),
507 sharing=self.get_argument('sharing'), public=self.get_argument('public'))
508 except ClientError as err:
512 class store_upload(_store_container_command):
515 def update_parser(self, parser):
516 super(self.__class__, self).update_parser(parser)
517 parser.add_argument('--use_hashes', action='store_true', dest='use_hashes', default=False,
518 help='provide hashmap file instead of data')
519 parser.add_argument('--unchunked', action='store_true', dest='unchunked', default=False,
520 help='avoid chunked transfer mode')
521 parser.add_argument('--etag', action='store', dest='etag', default=None,
522 help='check written data')
523 parser.add_argument('--content-encoding', action='store', dest='content_encoding',
524 default=None, help='provide the object MIME content type')
525 parser.add_argument('--content-disposition', action='store', dest='content_disposition',
526 default=None, help='provide the presentation style of the object')
527 parser.add_argument('--content-type', action='store', dest='content_type', default=None,
528 help='create object with specific content type')
529 parser.add_argument('--sharing', action='store', dest='sharing', default=None,
530 help='define sharing object policy ( "read=user1,grp1,user2,... write=user1,grp2,...')
531 parser.add_argument('--public', action='store_true', dest='public', default=False,
532 help='make object publicly accessible')
533 parser.add_argument('--with-pool-size', action='store', dest='poolsize', default=None,
534 help='Set the greenlet pool size (advanced)')
536 def getsharing(self, orelse={}):
537 permstr = getattr(self.args, 'sharing')
541 for p in permstr.split(' '):
542 (key, val) = p.split('=')
543 if key.lower() not in ('read', 'write'):
544 raise CLIError(message='in --sharing: Invalid permition key', importance=1)
545 val_list = val.split(',')
546 if not perms.has_key(key):
548 for item in val_list:
549 if item not in perms[key]:
550 perms[key].append(item)
553 def main(self, local_path, container____path__):
554 super(self.__class__, self).main(container____path__)
555 remote_path = local_path if self.path is None else self.path
556 poolsize = getattr(self.args, 'poolsize')
557 if poolsize is not None:
558 self.POOL_SIZE = int(poolsize)
560 with open(local_path) as f:
561 if getattr(self.args, 'unchunked'):
562 self.client.upload_object_unchunked(remote_path, f,
563 etag=getattr(self.args, 'etag'), withHashFile=getattr(self.args, 'use_hashes'),
564 content_encoding=getattr(self.args, 'content_encoding'),
565 content_disposition=getattr(self.args, 'content_disposition'),
566 content_type=getattr(self.args, 'content_type'), sharing=self.getsharing(),
567 public=getattr(self.args, 'public'))
569 hash_cb = self.progress('Calculating block hashes')
570 upload_cb = self.progress('Uploading blocks')
571 self.client.upload_object(remote_path, f, hash_cb=hash_cb, upload_cb=upload_cb,
572 content_encoding=getattr(self.args, 'content_encoding'),
573 content_disposition=getattr(self.args, 'content_disposition'),
574 content_type=getattr(self.args, 'content_type'), sharing=self.getsharing(),
575 public=getattr(self.args, 'public'))
576 except ClientError as err:
578 print 'Upload completed'
581 class store_download(_store_container_command):
582 """Download a file"""
584 def update_parser(self, parser):
585 super(self.__class__, self).update_parser(parser)
586 parser.add_argument('--no-progress-bar', action='store_true', dest='no_progress_bar',
587 default=False, help='Dont display progress bars')
588 parser.add_argument('--resume', action='store_true', dest='resume', default=False,
589 help='Resume a previous download instead of overwritting it')
590 parser.add_argument('--range', action='store', dest='range', default=None,
591 help='show range of data')
592 parser.add_argument('--if-match', action='store', dest='if_match', default=None,
593 help='show output if ETags match')
594 parser.add_argument('--if-none-match', action='store', dest='if_none_match', default=None,
595 help='show output if ETags don\'t match')
596 parser.add_argument('--if-modified-since', action='store', dest='if_modified_since',
597 default=None, help='show output if modified since then')
598 parser.add_argument('--if-unmodified-since', action='store', dest='if_unmodified_since',
599 default=None, help='show output if not modified since then')
600 parser.add_argument('--object-version', action='store', dest='object_version', default=None,
601 help='get the specific version')
602 parser.add_argument('--with-pool-size', action='store', dest='poolsize', default=None,
603 help='Set the greenlet pool size (advanced)')
605 def main(self, container___path, local_path=None):
606 super(self.__class__, self).main(container___path, path_is_optional=False)
610 if local_path is None:
614 if hasattr(self.args, 'resume') and getattr(self.args, 'resume'):
615 out=open(local_path, 'rwb+')
617 out=open(local_path, 'wb+')
618 except IOError as err:
619 raise CLIError(message='Cannot write to file %s - %s'%(local_path,unicode(err)),
621 download_cb = None if getattr(self.args, 'no_progress_bar') \
622 else self.progress('Downloading')
623 poolsize = getattr(self.args, 'poolsize')
624 if poolsize is not None:
625 self.POOL_SIZE = int(poolsize)
628 self.client.download_object(self.path, out, download_cb,
629 range=getattr(self.args, 'range'), version=getattr(self.args,'object_version'),
630 if_match=getattr(self.args, 'if_match'), resume=getattr(self.args, 'resume'),
631 if_none_match=getattr(self.args, 'if_none_match'),
632 if_modified_since=getattr(self.args, 'if_modified_since'),
633 if_unmodified_since=getattr(self.args, 'if_unmodified_since'))
634 except ClientError as err:
636 except KeyboardInterrupt:
637 print('\ndownload canceled by user')
638 if local_path is not None:
639 print('to resume, re-run with --resume')
643 class store_hashmap(_store_container_command):
644 """Get the hashmap of an object"""
646 def update_parser(self, parser):
647 super(self.__class__, self).update_parser(parser)
648 parser.add_argument('--if-match', action='store', dest='if_match', default=None,
649 help='show output if ETags match')
650 parser.add_argument('--if-none-match', action='store', dest='if_none_match', default=None,
651 help='show output if ETags dont match')
652 parser.add_argument('--if-modified-since', action='store', dest='if_modified_since',
653 default=None, help='show output if modified since then')
654 parser.add_argument('--if-unmodified-since', action='store', dest='if_unmodified_since',
655 default=None, help='show output if not modified since then')
656 parser.add_argument('--object-version', action='store', dest='object_version', default=None,
657 help='get the specific version')
659 def main(self, container___path):
660 super(self.__class__, self).main(container___path, path_is_optional=False)
662 data = self.client.get_object_hashmap(self.path,
663 version=getattr(self.args, 'object_version'),
664 if_match=getattr(self.args, 'if_match'),
665 if_none_match=getattr(self.args, 'if_none_match'),
666 if_modified_since=getattr(self.args, 'if_modified_since'),
667 if_unmodified_since=getattr(self.args, 'if_unmodified_since'))
668 except ClientError as err:
673 class store_delete(_store_container_command):
674 """Delete a container [or an object]"""
676 def update_parser(self, parser):
677 super(self.__class__, self).update_parser(parser)
678 parser.add_argument('--until', action='store', dest='until', default=None,
679 help='remove history until that date')
680 parser.add_argument('--format', action='store', dest='format', default='%d/%m/%Y %H:%M:%S',
681 help='format to parse until date (default: d/m/Y H:M:S)')
682 parser.add_argument('--delimiter', action='store', dest='delimiter',
684 help='mass delete objects with path staring with <object><delimiter>')
685 parser.add_argument('-r', action='store_true', dest='recursive', default=False,
686 help='empty dir or container and delete (if dir)')
688 def getuntil(self, orelse=None):
689 if hasattr(self.args, 'until'):
691 until = getattr(self.args, 'until')
694 format = getattr(self.args, 'format')
696 t = time.strptime(until, format)
697 except ValueError as err:
698 raise CLIError(message='in --until: '+unicode(err), importance=1)
699 return int(time.mktime(t))
702 def getdelimiter(self, orelse=None):
704 dlm = getattr(self.args, 'delimiter')
706 return '/' if getattr(self.args, 'recursive') else orelse
707 except AttributeError:
711 def main(self, container____path__):
712 super(self.__class__, self).main(container____path__)
714 if self.path is None:
715 self.client.del_container(until=self.getuntil(), delimiter=self.getdelimiter())
717 #self.client.delete_object(self.path)
718 self.client.del_object(self.path, until=self.getuntil(),
719 delimiter=self.getdelimiter())
720 except ClientError as err:
724 class store_purge(_store_container_command):
725 """Purge a container"""
727 def main(self, container):
728 super(self.__class__, self).main(container)
730 self.client.purge_container()
731 except ClientError as err:
735 class store_publish(_store_container_command):
736 """Publish an object"""
738 def main(self, container___path):
739 super(self.__class__, self).main(container___path, path_is_optional=False)
741 self.client.publish_object(self.path)
742 except ClientError as err:
746 class store_unpublish(_store_container_command):
747 """Unpublish an object"""
749 def main(self, container___path):
750 super(self.__class__, self).main(container___path, path_is_optional=False)
752 self.client.unpublish_object(self.path)
753 except ClientError as err:
757 class store_permitions(_store_container_command):
758 """Get object read/write permitions"""
760 def main(self, container___path):
761 super(self.__class__, self).main(container___path, path_is_optional=False)
763 reply = self.client.get_object_sharing(self.path)
765 except ClientError as err:
769 class store_setpermitions(_store_container_command):
770 """Set sharing permitions"""
772 def format_permition_dict(self,permitions):
775 for perms in permitions:
776 splstr = perms.split('=')
777 if 'read' == splstr[0]:
778 read = [user_or_group.strip() \
779 for user_or_group in splstr[1].split(',')]
780 elif 'write' == splstr[0]:
781 write = [user_or_group.strip() \
782 for user_or_group in splstr[1].split(',')]
786 if not read and not write:
787 raise CLIError(message='Usage:\tread=<groups,users> write=<groups,users>',
791 def main(self, container___path, *permitions):
792 super(self.__class__, self).main(container___path, path_is_optional=False)
793 (read, write) = self.format_permition_dict(permitions)
795 self.client.set_object_sharing(self.path,
796 read_permition=read, write_permition=write)
797 except ClientError as err:
801 class store_delpermitions(_store_container_command):
802 """Delete all sharing permitions"""
804 def main(self, container___path):
805 super(self.__class__, self).main(container___path, path_is_optional=False)
807 self.client.del_object_sharing(self.path)
808 except ClientError as err:
812 class store_info(_store_container_command):
813 """Get information for account [, container [or object]]"""
816 def main(self, container____path__=None):
817 super(self.__class__, self).main(container____path__)
819 if self.container is None:
820 reply = self.client.get_account_info()
821 elif self.path is None:
822 reply = self.client.get_container_info(self.container)
824 reply = self.client.get_object_info(self.path)
825 except ClientError as err:
830 class store_meta(_store_container_command):
831 """Get custom meta-content for account [, container [or object]]"""
833 def update_parser(self, parser):
834 super(self.__class__, self).update_parser(parser)
835 parser.add_argument('-l', action='store_true', dest='detail', default=False,
836 help='show detailed output')
837 parser.add_argument('--until', action='store', dest='until', default=None,
838 help='show metadata until that date')
839 dateformat='%d/%m/%Y %H:%M:%S'
840 parser.add_argument('--format', action='store', dest='format', default=dateformat,
841 help='format to parse until date (default: "d/m/Y H:M:S")')
842 parser.add_argument('--object_version', action='store', dest='object_version', default=None,
843 help='show specific version \ (applies only for objects)')
845 def getuntil(self, orelse=None):
846 if hasattr(self.args, 'until'):
848 until = getattr(self.args, 'until')
851 format = getattr(self.args, 'format')
854 t = time.strptime(until, format)
855 except ValueError as err:
856 raise CLIError(message='in --until: '+unicode(err), importance=1)
857 return int(time.mktime(t))
860 def main(self, container____path__ = None):
861 super(self.__class__, self).main(container____path__)
863 detail = getattr(self.args, 'detail')
865 if self.container is None:
866 print(bold(self.client.account))
868 reply = self.client.get_account_info(until=self.getuntil())
870 reply = self.client.get_account_meta(until=self.getuntil())
871 reply = pretty_keys(reply, '-')
872 elif self.path is None:
873 print(bold(self.client.account+': '+self.container))
875 reply = self.client.get_container_info(until = self.getuntil())
877 cmeta = self.client.get_container_meta(until=self.getuntil())
878 ometa = self.client.get_container_object_meta(until=self.getuntil())
879 reply = {'container-meta':pretty_keys(cmeta, '-'),
880 'object-meta':pretty_keys(ometa, '-')}
882 print(bold(self.client.account+': '+self.container+':'+self.path))
883 version=getattr(self.args, 'object_version')
885 reply = self.client.get_object_info(self.path, version = version)
887 reply = self.client.get_object_meta(self.path, version=version)
888 reply = pretty_keys(pretty_keys(reply, '-'))
889 except ClientError as err:
894 class store_setmeta(_store_container_command):
895 """Set a new metadatum for account [, container [or object]]"""
897 def main(self, metakey___metaval, container____path__=None):
898 super(self.__class__, self).main(container____path__)
900 metakey, metavalue = metakey___metaval.split(':')
902 raise CLIError(message='Meta variables should be formated as metakey:metavalue',
905 if self.container is None:
906 self.client.set_account_meta({metakey:metavalue})
907 elif self.path is None:
908 self.client.set_container_meta({metakey:metavalue})
910 self.client.set_object_meta(self.path, {metakey:metavalue})
911 except ClientError as err:
915 class store_delmeta(_store_container_command):
916 """Delete an existing metadatum of account [, container [or object]]"""
918 def main(self, metakey, container____path__=None):
919 super(self.__class__, self).main(container____path__)
921 if self.container is None:
922 self.client.del_account_meta(metakey)
923 elif self.path is None:
924 self.client.del_container_meta(metakey)
926 self.client.del_object_meta(metakey, self.path)
927 except ClientError as err:
931 class store_quota(_store_account_command):
932 """Get quota for account [or container]"""
934 def main(self, container = None):
935 super(self.__class__, self).main()
937 if container is None:
938 reply = self.client.get_account_quota()
940 reply = self.client.get_container_quota(container)
941 except ClientError as err:
946 class store_setquota(_store_account_command):
947 """Set new quota (in KB) for account [or container]"""
949 def main(self, quota, container = None):
950 super(self.__class__, self).main()
952 if container is None:
953 self.client.set_account_quota(quota)
955 self.client.container = container
956 self.client.set_container_quota(quota)
957 except ClientError as err:
961 class store_versioning(_store_account_command):
962 """Get versioning for account [or container ]"""
964 def main(self, container = None):
965 super(self.__class__, self).main()
967 if container is None:
968 reply = self.client.get_account_versioning()
970 reply = self.client.get_container_versioning(container)
971 except ClientError as err:
976 class store_setversioning(_store_account_command):
977 """Set new versioning (auto, none) for account [or container]"""
979 def main(self, versioning, container = None):
980 super(self.__class__, self).main()
982 if container is None:
983 self.client.set_account_versioning(versioning)
985 self.client.container = container
986 self.client.set_container_versioning(versioning)
987 except ClientError as err:
991 class store_group(_store_account_command):
992 """Get user groups details for account"""
995 super(self.__class__, self).main()
997 reply = self.client.get_account_group()
998 except ClientError as err:
1003 class store_setgroup(_store_account_command):
1004 """Create/update a new user group on account"""
1006 def main(self, groupname, *users):
1007 super(self.__class__, self).main()
1009 self.client.set_account_group(groupname, users)
1010 except ClientError as err:
1014 class store_delgroup(_store_account_command):
1015 """Delete a user group on an account"""
1017 def main(self, groupname):
1018 super(self.__class__, self).main()
1020 self.client.del_account_group(groupname)
1021 except ClientError as err:
1025 class store_sharers(_store_account_command):
1026 """List the accounts that share objects with default account"""
1028 def update_parser(self, parser):
1029 super(store_sharers, self).update_parser(parser)
1030 parser.add_argument('-l', action='store_true', dest='detail', default=False,
1031 help='show detailed output')
1032 parser.add_argument('-n', action='store', dest='limit', default=10000,
1033 help='show limited output')
1034 parser.add_argument('--marker', action='store', dest='marker', default=None,
1035 help='show output greater then marker')
1038 super(self.__class__, self).main()
1040 accounts = self.client.get_sharing_accounts(marker=getattr(self.args, 'marker'))
1041 except ClientError as err:
1044 for acc in accounts:
1045 stdout.write(bold(acc['name'])+' ')
1046 if getattr(self.args, 'detail'):
1047 print_dict(acc, exclude='name', ident=18)
1048 if not getattr(self.args, 'detail'):
1052 class store_versions(_store_container_command):
1053 """Get the version list of an object"""
1055 def main(self, container___path):
1056 super(store_versions, self).main(container___path)
1058 versions = self.client.get_object_versionlist(self.path)
1059 except ClientError as err:
1062 print('%s:%s versions'%(self.container,self.path))
1063 for vitem in versions:
1064 t = localtime(float(vitem[1]))
1065 vid = bold(unicode(vitem[0]))
1066 print('\t%s \t(%s)'%(vid, strftime('%d-%m-%Y %H:%M:%S', t)))