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, CLIError
35 from kamaki.clients.utils import filter_in
36 from kamaki.cli.utils import format_size, raiseCLIError, print_dict, pretty_keys, print_list
37 set_api_description('store', 'Pithos+ storage commands')
38 from kamaki.clients.pithos import PithosClient, ClientError
39 from colors import bold
40 from sys import stdout, exit
43 from progress.bar import IncrementalBar
46 class ProgressBar(IncrementalBar):
47 #suffix = '%(percent)d%% - %(eta)ds'
48 suffix = '%(percent)d%%'
50 class _pithos_init(object):
52 self.token = self.config.get('store', 'token') or self.config.get('global', 'token')
53 self.base_url = self.config.get('store', 'url') or self.config.get('global', 'url')
54 self.account = self.config.get('store', 'account') or self.config.get('global', 'account')
55 self.container = self.config.get('store', 'container') or self.config.get('global', 'container')
56 self.client = PithosClient(base_url=self.base_url, token=self.token, account=self.account,
57 container=self.container)
59 class _store_account_command(_pithos_init):
60 """Base class for account level storage commands"""
62 def update_parser(self, parser):
63 parser.add_argument('--account', dest='account', metavar='NAME',
64 help="Specify an account to use")
66 def progress(self, message):
67 """Return a generator function to be used for progress tracking"""
72 msg = message.ljust(MESSAGE_LENGTH)
73 for i in ProgressBar(msg).iter(range(n)):
80 super(_store_account_command, self).main()
81 if hasattr(self.args, 'account') and self.args.account is not None:
82 self.client.account = self.args.account
84 class _store_container_command(_store_account_command):
85 """Base class for container level storage commands"""
91 def update_parser(self, parser):
92 super(_store_container_command, self).update_parser(parser)
93 parser.add_argument('--container', dest='container', metavar='NAME', default=None,
94 help="Specify a container to use")
96 def extract_container_and_path(self, container_with_path, path_is_optional=True):
97 assert isinstance(container_with_path, str)
98 if ':' not in container_with_path:
99 if hasattr(self.args, 'container'):
100 self.container = getattr(self.args, 'container')
102 self.container = self.client.container
103 if self.container is None:
104 self.container = container_with_path
106 self.path = container_with_path
107 if not path_is_optional and self.path is None:
108 raise CLIError(message="Object path is missing", status=11)
110 cnp = container_with_path.split(':')
111 self.container = cnp[0]
118 raise CLIError(message="Object path is missing", status=11)
120 def main(self, container_with_path=None, path_is_optional=True):
121 super(_store_container_command, self).main()
122 if container_with_path is not None:
123 self.extract_container_and_path(container_with_path, path_is_optional)
124 self.client.container = self.container
125 elif hasattr(self.args, 'container'):
126 self.client.container = getattr(self.args,'container')
127 self.container = self.client.container
131 class store_test(_store_container_command):
135 super(self.__class__, self).main('pithos')
136 r = self.client.container_get()
137 print(unicode(r.content)+' '+unicode(r.json))
141 class store_list(_store_container_command):
142 """List containers, object trees or objects in a directory
145 def update_parser(self, parser):
146 super(self.__class__, self).update_parser(parser)
147 parser.add_argument('-l', action='store_true', dest='detail', default=False,
148 help='show detailed output')
149 parser.add_argument('-N', action='store', dest='show_size', default=1000,
150 help='print output in chunks of size N')
151 parser.add_argument('-n', action='store', dest='limit', default=None,
152 help='show limited output')
153 parser.add_argument('--marker', action='store', dest='marker', default=None,
154 help='show output greater then marker')
155 parser.add_argument('--prefix', action='store', dest='prefix', default=None,
156 help='show output starting with prefix')
157 parser.add_argument('--delimiter', action='store', dest='delimiter', default=None,
158 help='show output up to the delimiter')
159 parser.add_argument('--path', action='store', dest='path', default=None,
160 help='show output starting with prefix up to /')
161 parser.add_argument('--meta', action='store', dest='meta', default=None,
162 help='show output having the specified meta keys (e.g. --meta "meta1 meta2 ..."')
163 parser.add_argument('--if-modified-since', action='store', dest='if_modified_since',
164 default=None, help='show output if modified since then')
165 parser.add_argument('--if-unmodified-since', action='store', dest='if_unmodified_since',
166 default=None, help='show output if not modified since then')
167 parser.add_argument('--until', action='store', dest='until', default=None,
168 help='show metadata until that date')
169 dateformat = '%d/%m/%Y %H:%M:%S'
170 parser.add_argument('--format', action='store', dest='format', default=dateformat,
171 help='format to parse until date (default: d/m/Y H:M:S)')
172 parser.add_argument('--shared', action='store_true', dest='shared', default=False,
173 help='show only shared')
174 parser.add_argument('--public', action='store_true', dest='public', default=False,
175 help='show only public')
177 def print_objects(self, object_list):
180 limit = getattr(self.args, 'show_size')
182 except AttributeError:
185 for index,obj in enumerate(object_list):
186 if not obj.has_key('content_type'):
188 pretty_obj = obj.copy()
190 empty_space = ' '*(len(str(len(object_list))) - len(str(index)))
191 if obj['content_type'] == 'application/directory':
196 size = format_size(obj['bytes'])
197 pretty_obj['bytes'] = '%s (%s)'%(obj['bytes'],size)
198 oname = bold(obj['name'])
199 if getattr(self.args, 'detail'):
200 print('%s%s. %s'%(empty_space, index, oname))
201 print_dict(pretty_keys(pretty_obj), exclude=('name'))
204 oname = '%s%s. %6s %s'%(empty_space, index, size, oname)
205 oname += '/' if isDir else ''
207 if limit <= index < len(object_list) and index%limit == 0:
208 print('(press "enter" to continue)')
211 def print_containers(self, container_list):
214 limit = getattr(self.args, 'show_size')
216 except AttributeError:
218 for index,container in enumerate(container_list):
219 if container.has_key('bytes'):
220 size = format_size(container['bytes'])
221 cname = '%s. %s'%(index+1, bold(container['name']))
222 if getattr(self.args, 'detail'):
224 pretty_c = container.copy()
225 if container.has_key('bytes'):
226 pretty_c['bytes'] = '%s (%s)'%(container['bytes'], size)
227 print_dict(pretty_keys(pretty_c), exclude=('name'))
230 if container.has_key('count') and container.has_key('bytes'):
231 print('%s (%s, %s objects)' % (cname, size, container['count']))
234 if limit <= index < len(container_list) and index%limit == 0:
235 print('(press "enter" to continue)')
238 def getuntil(self, orelse=None):
239 if hasattr(self.args, 'until'):
241 until = getattr(self.args, 'until')
244 format = getattr(self.args, 'format')
247 t = time.strptime(until, format)
248 except ValueError as err:
249 raise CLIError(message='in --until: '+unicode(err), importance=1)
250 return int(time.mktime(t))
253 def getmeta(self, orelse=[]):
254 if hasattr(self.args, 'meta'):
255 meta = getattr(self.args, 'meta')
258 return meta.split(' ')
261 def getpath(self, orelse=None):
262 if self.path is not None:
264 if hasattr(self.args, 'path'):
265 return getattr(self.args, 'path')
268 def main(self, container____path__=None):
269 super(self.__class__, self).main(container____path__)
271 if self.container is None:
272 r = self.client.account_get(limit=getattr(self.args, 'limit', None),
273 marker=getattr(self.args, 'marker', None),
274 if_modified_since=getattr(self.args, 'if_modified_since', None),
275 if_unmodified_since=getattr(self.args, 'if_unmodified_since', None),
276 until=self.getuntil(),
277 show_only_shared=getattr(self.args, 'shared', False))
278 self.print_containers(r.json)
280 r = self.client.container_get(limit=getattr(self.args, 'limit', None),
281 marker=getattr(self.args, 'marker', None),
282 prefix=getattr(self.args, 'prefix', None),
283 delimiter=getattr(self.args, 'delimiter', None), path=self.getpath(orelse=None),
284 if_modified_since=getattr(self.args, 'if_modified_since', None),
285 if_unmodified_since=getattr(self.args, 'if_unmodified_since', None),
286 until=self.getuntil(),
288 show_only_shared=getattr(self.args, 'shared', False))
289 self.print_objects(r.json)
290 except ClientError as err:
294 class store_mkdir(_store_container_command):
295 """Create a directory"""
297 def main(self, container___directory):
298 super(self.__class__, self).main(container___directory, path_is_optional=False)
300 self.client.create_directory(self.path)
301 except ClientError as err:
305 class store_create(_store_container_command):
306 """Create a container or a directory object"""
308 def update_parser(self, parser):
309 super(self.__class__, self).update_parser(parser)
310 parser.add_argument('--versioning', action='store', dest='versioning', default=None,
311 help='set container versioning (auto/none)')
312 parser.add_argument('--quota', action='store', dest='quota', default=None,
313 help='set default container quota')
314 parser.add_argument('--meta', action='store', dest='meta', default=None,
315 help='set container metadata ("key1:val1 key2:val2 ...")')
317 def getmeta(self, orelse=None):
319 meta = getattr(self.args,'meta')
320 metalist = meta.split(' ')
321 except AttributeError:
324 for metastr in metalist:
325 (key,val) = metastr.split(':')
329 def main(self, container____directory__):
330 super(self.__class__, self).main(container____directory__)
332 if self.path is None:
333 self.client.container_put(quota=getattr(self.args, 'quota'),
334 versioning=getattr(self.args, 'versioning'), metadata=self.getmeta())
336 self.client.create_directory(self.path)
337 except ClientError as err:
341 class store_copy(_store_container_command):
344 def update_parser(self, parser):
345 super(store_copy, self).update_parser(parser)
346 parser.add_argument('--source-version', action='store', dest='source_version', default=None,
347 help='copy specific version')
348 parser.add_argument('--public', action='store_true', dest='public', default=False,
349 help='make object publicly accessible')
350 parser.add_argument('--content-type', action='store', dest='content_type', default=None,
351 help='change object\'s content type')
352 parser.add_argument('--delimiter', action='store', dest='delimiter', default=None,
353 help=u'mass copy objects with path staring with src_object + delimiter')
354 parser.add_argument('-r', action='store_true', dest='recursive', default=False,
355 help='mass copy with delimiter /')
357 def getdelimiter(self):
358 if getattr(self.args, 'recursive'):
360 return getattr(self.args, 'delimiter')
362 def main(self, source_container___path, destination_container____path__):
363 super(self.__class__, self).main(source_container___path, path_is_optional=False)
365 dst = destination_container____path__.split(':')
367 dst_path = dst[1] if len(dst) > 1 else False
368 self.client.copy_object(src_container = self.container, src_object = self.path,
369 dst_container = dst_cont, dst_object = dst_path,
370 source_version=getattr(self.args, 'source_version'),
371 public=getattr(self.args, 'public'),
372 content_type=getattr(self.args,'content_type'), delimiter=self.getdelimiter())
373 except ClientError as err:
377 class store_move(_store_container_command):
380 def update_parser(self, parser):
381 super(store_move, self).update_parser(parser)
382 parser.add_argument('--source-version', action='store', dest='source_version', default=None,
383 help='copy specific version')
384 parser.add_argument('--public', action='store_true', dest='public', default=False,
385 help='make object publicly accessible')
386 parser.add_argument('--content-type', action='store', dest='content_type', default=None,
387 help='change object\'s content type')
388 parser.add_argument('--delimiter', action='store', dest='delimiter', default=None,
389 help=u'mass copy objects with path staring with src_object + delimiter')
390 parser.add_argument('-r', action='store_true', dest='recursive', default=False,
391 help='mass copy with delimiter /')
393 def getdelimiter(self):
394 if getattr(self.args, 'recursive'):
396 return getattr(self.args, 'delimiter')
398 def main(self, source_container___path, destination_container____path__):
399 super(self.__class__, self).main(source_container___path, path_is_optional=False)
401 dst = destination_container____path__.split(':')
403 dst_path = dst[1] if len(dst) > 1 else False
404 self.client.move_object(src_container = self.container, src_object = self.path,
405 dst_container = dst_cont, dst_object = dst_path,
406 source_version=getattr(self.args, 'source_version'),
407 public=getattr(self.args, 'public'),
408 content_type=getattr(self.args,'content_type'), delimiter=self.getdelimiter())
409 except ClientError as err:
413 class store_append(_store_container_command):
414 """Append local file to (existing) remote object"""
416 def main(self, local_path, container___path):
417 super(self.__class__, self).main(container___path, path_is_optional=False)
419 f = open(local_path, 'r')
420 upload_cb = self.progress('Appending blocks')
421 self.client.append_object(object=self.path, source_file = f, upload_cb = upload_cb)
422 except ClientError as err:
426 class store_truncate(_store_container_command):
427 """Truncate remote file up to a size"""
430 def main(self, container___path, size=0):
431 super(self.__class__, self).main(container___path, path_is_optional=False)
433 self.client.truncate_object(self.path, size)
434 except ClientError as err:
438 class store_overwrite(_store_container_command):
439 """Overwrite part (from start to end) of a remote file"""
441 def main(self, local_path, container___path, start, end):
442 super(self.__class__, self).main(container___path, path_is_optional=False)
444 f = open(local_path, 'r')
445 upload_cb = self.progress('Overwritting blocks')
446 self.client.overwrite_object(object=self.path, start=start, end=end,
447 source_file=f, upload_cb = upload_cb)
448 except ClientError as err:
452 class store_manifest(_store_container_command):
453 """Create a remote file with uploaded parts by manifestation"""
455 def update_parser(self, parser):
456 super(self.__class__, self).update_parser(parser)
457 parser.add_argument('--etag', action='store', dest='etag', default=None,
458 help='check written data')
459 parser.add_argument('--content-encoding', action='store', dest='content_encoding',
460 default=None, help='provide the object MIME content type')
461 parser.add_argument('--content-disposition', action='store', dest='content_disposition',
462 default=None, help='provide the presentation style of the object')
463 parser.add_argument('--content-type', action='store', dest='content_type', default=None,
464 help='create object with specific content type')
465 parser.add_argument('--sharing', action='store', dest='sharing', default=None,
466 help='define sharing object policy ( "read=user1,grp1,user2,... write=user1,grp2,...')
467 parser.add_argument('--public', action='store_true', dest='public', default=False,
468 help='make object publicly accessible')
470 def getsharing(self, orelse={}):
471 permstr = getattr(self.args, 'sharing')
475 for p in permstr.split(' '):
476 (key, val) = p.split('=')
477 if key.lower() not in ('read', 'write'):
478 raise CLIError(message='in --sharing: Invalid permition key', importance=1)
479 val_list = val.split(',')
480 if not perms.has_key(key):
482 for item in val_list:
483 if item not in perms[key]:
484 perms[key].append(item)
487 def main(self, container___path):
488 super(self.__class__, self).main(container___path, path_is_optional=False)
490 self.client.create_object_by_manifestation(self.path,
491 content_encoding=getattr(self.args, 'content_encoding'),
492 content_disposition=getattr(self.args, 'content_disposition'),
493 content_type=getattr(self.args, 'content_type'), sharing=self.getsharing(),
494 public=getattr(self.args, 'public'))
495 except ClientError as err:
499 class store_upload(_store_container_command):
502 def update_parser(self, parser):
503 super(self.__class__, self).update_parser(parser)
504 parser.add_argument('--use_hashes', action='store_true', dest='use_hashes', default=False,
505 help='provide hashmap file instead of data')
506 parser.add_argument('--unchunked', action='store_true', dest='unchunked', default=False,
507 help='avoid chunked transfer mode')
508 parser.add_argument('--etag', action='store', dest='etag', default=None,
509 help='check written data')
510 parser.add_argument('--content-encoding', action='store', dest='content_encoding',
511 default=None, help='provide the object MIME content type')
512 parser.add_argument('--content-disposition', action='store', dest='content_disposition',
513 default=None, help='provide the presentation style of the object')
514 parser.add_argument('--content-type', action='store', dest='content_type', default=None,
515 help='create object with specific content type')
516 parser.add_argument('--sharing', action='store', dest='sharing', default=None,
517 help='define sharing object policy ( "read=user1,grp1,user2,... write=user1,grp2,...')
518 parser.add_argument('--public', action='store_true', dest='public', default=False,
519 help='make object publicly accessible')
521 def getsharing(self, orelse={}):
522 permstr = getattr(self.args, 'sharing')
526 for p in permstr.split(' '):
527 (key, val) = p.split('=')
528 if key.lower() not in ('read', 'write'):
529 raise CLIError(message='in --sharing: Invalid permition key', importance=1)
530 val_list = val.split(',')
531 if not perms.has_key(key):
533 for item in val_list:
534 if item not in perms[key]:
535 perms[key].append(item)
538 def main(self, local_path, container____path__):
539 super(self.__class__, self).main(container____path__)
540 remote_path = local_path if self.path is None else self.path
542 with open(local_path) as f:
543 if getattr(self.args, 'unchunked'):
544 self.client.upload_object_unchunked(remote_path, f,
545 etag=getattr(self.args, 'etag'), withHashFile=getattr(self.args, 'use_hashes'),
546 content_encoding=getattr(self.args, 'content_encoding'),
547 content_disposition=getattr(self.args, 'content_disposition'),
548 content_type=getattr(self.args, 'content_type'), sharing=self.getsharing(),
549 public=getattr(self.args, 'public'))
551 hash_cb = self.progress('Calculating block hashes')
552 upload_cb = self.progress('Uploading blocks')
553 self.client.upload_object(remote_path, f, hash_cb=hash_cb, upload_cb=upload_cb,
554 content_encoding=getattr(self.args, 'content_encoding'),
555 content_disposition=getattr(self.args, 'content_disposition'),
556 content_type=getattr(self.args, 'content_type'), sharing=self.getsharing(),
557 public=getattr(self.args, 'public'))
558 except ClientError as err:
560 print 'Upload completed'
563 class store_download(_store_container_command):
564 """Download a file"""
566 def update_parser(self, parser):
567 super(self.__class__, self).update_parser(parser)
568 parser.add_argument('--no-progress-bar', action='store_true', dest='no_progress_bar',
569 default=False, help='Dont display progress bars')
570 parser.add_argument('--overide', action='store_true', dest='overide', default=False,
571 help='Force download to overide an existing file')
572 parser.add_argument('--range', action='store', dest='range', default=None,
573 help='show range of data')
574 parser.add_argument('--if-match', action='store', dest='if_match', default=None,
575 help='show output if ETags match')
576 parser.add_argument('--if-none-match', action='store', dest='if_none_match', default=None,
577 help='show output if ETags don\'t match')
578 parser.add_argument('--if-modified-since', action='store', dest='if_modified_since',
579 default=None, help='show output if modified since then')
580 parser.add_argument('--if-unmodified-since', action='store', dest='if_unmodified_since',
581 default=None, help='show output if not modified since then')
582 parser.add_argument('--object-version', action='store', dest='object_version', default=None,
583 help='get the specific version')
585 def main(self, container___path, local_path=None):
586 super(self.__class__, self).main(container___path, path_is_optional=False)
590 if local_path is None:
594 if getattr(self.args, 'overide'):
595 out = open(local_path, 'wb+')
597 out = open(local_path, 'ab+')
598 except IOError as err:
599 raise CLIError(message='Cannot write to file %s - %s'%(local_path,unicode(err)),
601 download_cb = None if getattr(self.args, 'no_progress_bar') \
602 else self.progress('Downloading')
606 self.client.download_object(self.path, out, download_cb,
607 range=getattr(self.args, 'range'), version=getattr(self.args,'object_version'),
608 if_match=getattr(self.args, 'if_match'), overide=getattr(self.args, 'overide'),
609 if_none_match=getattr(self.args, 'if_none_match'),
610 if_modified_since=getattr(self.args, 'if_modified_since'),
611 if_unmodified_since=getattr(self.args, 'if_unmodified_since'))
612 except ClientError as err:
614 except KeyboardInterrupt:
615 print('\ndownload canceled by user')
616 if local_path is not None:
617 print('re-run command to resume')
621 class store_hashmap(_store_container_command):
622 """Get the hashmap of an object"""
624 def update_parser(self, parser):
625 super(self.__class__, self).update_parser(parser)
626 parser.add_argument('--if-match', action='store', dest='if_match', default=None,
627 help='show output if ETags match')
628 parser.add_argument('--if-none-match', action='store', dest='if_none_match', default=None,
629 help='show output if ETags dont match')
630 parser.add_argument('--if-modified-since', action='store', dest='if_modified_since',
631 default=None, help='show output if modified since then')
632 parser.add_argument('--if-unmodified-since', action='store', dest='if_unmodified_since',
633 default=None, help='show output if not modified since then')
634 parser.add_argument('--object-version', action='store', dest='object_version', default=None,
635 help='get the specific version')
637 def main(self, container___path):
638 super(self.__class__, self).main(container___path, path_is_optional=False)
640 data = self.client.get_object_hashmap(self.path,
641 version=getattr(self.args, 'object_version'),
642 if_match=getattr(self.args, 'if_match'),
643 if_none_match=getattr(self.args, 'if_none_match'),
644 if_modified_since=getattr(self.args, 'if_modified_since'),
645 if_unmodified_since=getattr(self.args, 'if_unmodified_since'))
646 except ClientError as err:
651 class store_delete(_store_container_command):
652 """Delete a container [or an object]"""
654 def update_parser(self, parser):
655 super(self.__class__, self).update_parser(parser)
656 parser.add_argument('--until', action='store', dest='until', default=None,
657 help='remove history until that date')
658 parser.add_argument('--format', action='store', dest='format', default='%d/%m/%Y %H:%M:%S',
659 help='format to parse until date (default: d/m/Y H:M:S)')
660 parser.add_argument('--delimiter', action='store', dest='delimiter',
662 help='mass delete objects with path staring with <object><delimiter>')
663 parser.add_argument('-r', action='store_true', dest='recursive', default=False,
664 help='empty dir or container and delete (if dir)')
666 def getuntil(self, orelse=None):
667 if hasattr(self.args, 'until'):
669 until = getattr(self.args, 'until')
672 format = getattr(self.args, 'format')
674 t = time.strptime(until, format)
675 except ValueError as err:
676 raise CLIError(message='in --until: '+unicode(err), importance=1)
677 return int(time.mktime(t))
680 def getdelimiter(self, orelse=None):
682 dlm = getattr(self.args, 'delimiter')
684 return '/' if getattr(self.args, 'recursive') else orelse
685 except AttributeError:
689 def main(self, container____path__):
690 super(self.__class__, self).main(container____path__)
692 if self.path is None:
693 self.client.del_container(until=self.getuntil(), delimiter=self.getdelimiter())
695 #self.client.delete_object(self.path)
696 self.client.del_object(self.path, until=self.getuntil(),
697 delimiter=self.getdelimiter())
698 except ClientError as err:
702 class store_purge(_store_container_command):
703 """Purge a container"""
705 def main(self, container):
706 super(self.__class__, self).main()
708 self.client.purge_container()
709 except ClientError as err:
713 class store_publish(_store_container_command):
714 """Publish an object"""
716 def main(self, container___path):
717 super(self.__class__, self).main(container___path, path_is_optional=False)
719 self.client.publish_object(self.path)
720 except ClientError as err:
724 class store_unpublish(_store_container_command):
725 """Unpublish an object"""
727 def main(self, container___path):
728 super(self.__class__, self).main(container___path, path_is_optional=False)
730 self.client.unpublish_object(self.path)
731 except ClientError as err:
735 class store_permitions(_store_container_command):
736 """Get object read/write permitions"""
738 def main(self, container___path):
739 super(self.__class__, self).main(container___path, path_is_optional=False)
741 reply = self.client.get_object_sharing(self.path)
743 except ClientError as err:
747 class store_setpermitions(_store_container_command):
748 """Set sharing permitions"""
750 def format_permition_dict(self,permitions):
753 for perms in permitions:
754 splstr = perms.split('=')
755 if 'read' == splstr[0]:
756 read = [user_or_group.strip() \
757 for user_or_group in splstr[1].split(',')]
758 elif 'write' == splstr[0]:
759 write = [user_or_group.strip() \
760 for user_or_group in splstr[1].split(',')]
764 if not read and not write:
765 raise CLIError(message='Usage:\tread=<groups,users> write=<groups,users>',
769 def main(self, container___path, *permitions):
770 super(self.__class__, self).main(container___path, path_is_optional=False)
771 (read, write) = self.format_permition_dict(permitions)
773 self.client.set_object_sharing(self.path,
774 read_permition=read, write_permition=write)
775 except ClientError as err:
779 class store_delpermitions(_store_container_command):
780 """Delete all sharing permitions"""
782 def main(self, container___path):
783 super(self.__class__, self).main(container___path, path_is_optional=False)
785 self.client.del_object_sharing(self.path)
786 except ClientError as err:
790 class store_info(_store_container_command):
791 """Get information for account [, container [or object]]"""
794 def main(self, container____path__=None):
795 super(self.__class__, self).main(container____path__)
797 if self.container is None:
798 reply = self.client.get_account_info()
799 elif self.path is None:
800 reply = self.client.get_container_info(self.container)
802 reply = self.client.get_object_info(self.path)
803 except ClientError as err:
808 class store_meta(_store_container_command):
809 """Get custom meta-content for account [, container [or object]]"""
811 def update_parser(self, parser):
812 super(self.__class__, self).update_parser(parser)
813 parser.add_argument('-l', action='store_true', dest='detail', default=False,
814 help='show detailed output')
815 parser.add_argument('--until', action='store', dest='until', default=None,
816 help='show metadata until that date')
817 dateformat='%d/%m/%Y %H:%M:%S'
818 parser.add_argument('--format', action='store', dest='format', default=dateformat,
819 help='format to parse until date (default: "d/m/Y H:M:S")')
820 parser.add_argument('--object_version', action='store', dest='object_version', default=None,
821 help='show specific version \ (applies only for objects)')
823 def getuntil(self, orelse=None):
824 if hasattr(self.args, 'until'):
826 until = getattr(self.args, 'until')
829 format = getattr(self.args, 'format')
832 t = time.strptime(until, format)
833 except ValueError as err:
834 raise CLIError(message='in --until: '+unicode(err), importance=1)
835 return int(time.mktime(t))
838 def main(self, container____path__ = None):
839 super(self.__class__, self).main(container____path__)
841 detail = getattr(self.args, 'detail')
843 if self.container is None:
844 print(bold(self.client.account))
846 reply = self.client.get_account_info(until=self.getuntil())
848 reply = self.client.get_account_meta(until=self.getuntil())
849 reply = pretty_keys(reply, '-')
850 elif self.path is None:
851 print(bold(self.client.account+': '+self.container))
853 reply = self.client.get_container_info(until = self.getuntil())
855 cmeta = self.client.get_container_meta(until=self.getuntil())
856 ometa = self.client.get_container_object_meta(until=self.getuntil())
857 reply = {'container-meta':pretty_keys(cmeta, '-'),
858 'object-meta':pretty_keys(ometa, '-')}
860 print(bold(self.client.account+': '+self.container+':'+self.path))
861 version=getattr(self.args, 'object_version')
863 reply = self.client.get_object_info(self.path, version = version)
865 reply = self.client.get_object_meta(self.path, version=version)
866 reply = pretty_keys(pretty_keys(reply, '-'))
867 except ClientError as err:
872 class store_setmeta(_store_container_command):
873 """Set a new metadatum for account [, container [or object]]"""
875 def main(self, metakey___metaval, container____path__=None):
876 super(self.__class__, self).main(container____path__)
878 metakey, metavalue = metakey___metaval.split(':')
880 raise CLIError(message='Meta variables should be formated as metakey:metavalue',
883 if self.container is None:
884 self.client.set_account_meta({metakey:metavalue})
885 elif self.path is None:
886 self.client.set_container_meta({metakey:metavalue})
888 self.client.set_object_meta(self.path, {metakey:metavalue})
889 except ClientError as err:
893 class store_delmeta(_store_container_command):
894 """Delete an existing metadatum of account [, container [or object]]"""
896 def main(self, metakey, container____path__=None):
897 super(self.__class__, self).main(container____path__)
899 if self.container is None:
900 self.client.del_account_meta(metakey)
901 elif self.path is None:
902 self.client.del_container_meta(metakey)
904 self.client.del_object_meta(metakey, self.path)
905 except ClientError as err:
909 class store_quota(_store_account_command):
910 """Get quota for account [or container]"""
912 def main(self, container = None):
913 super(self.__class__, self).main()
915 if container is None:
916 reply = self.client.get_account_quota()
918 reply = self.client.get_container_quota(container)
919 except ClientError as err:
924 class store_setquota(_store_account_command):
925 """Set new quota (in KB) for account [or container]"""
927 def main(self, quota, container = None):
928 super(self.__class__, self).main()
930 if container is None:
931 self.client.set_account_quota(quota)
933 self.client.container = container
934 self.client.set_container_quota(quota)
935 except ClientError as err:
939 class store_versioning(_store_account_command):
940 """Get versioning for account [or container ]"""
942 def main(self, container = None):
943 super(self.__class__, self).main()
945 if container is None:
946 reply = self.client.get_account_versioning()
948 reply = self.client.get_container_versioning(container)
949 except ClientError as err:
954 class store_setversioning(_store_account_command):
955 """Set new versioning (auto, none) for account [or container]"""
957 def main(self, versioning, container = None):
958 super(self.__class__, self).main()
960 if container is None:
961 self.client.set_account_versioning(versioning)
963 self.client.container = container
964 self.client.set_container_versioning(versioning)
965 except ClientError as err:
969 class store_group(_store_account_command):
970 """Get user groups details for account"""
973 super(self.__class__, self).main()
975 reply = self.client.get_account_group()
976 except ClientError as err:
981 class store_setgroup(_store_account_command):
982 """Create/update a new user group on account"""
984 def main(self, groupname, *users):
985 super(self.__class__, self).main()
987 self.client.set_account_group(groupname, users)
988 except ClientError as err:
992 class store_delgroup(_store_account_command):
993 """Delete a user group on an account"""
995 def main(self, groupname):
996 super(self.__class__, self).main()
998 self.client.del_account_group(groupname)
999 except ClientError as err: