root / kamaki / cli / commands / pithos_cli.py @ 7147e1ca
History | View | Annotate | Download (75.2 kB)
1 |
# Copyright 2011-2012 GRNET S.A. All rights reserved.
|
---|---|
2 |
#
|
3 |
# Redistribution and use in source and binary forms, with or
|
4 |
# without modification, are permitted provided that the following
|
5 |
# conditions are met:
|
6 |
#
|
7 |
# 1. Redistributions of source code must retain the above
|
8 |
# copyright notice, this list of conditions and the following
|
9 |
# disclaimer.
|
10 |
#
|
11 |
# 2. Redistributions in binary form must reproduce the above
|
12 |
# copyright notice, this list of conditions and the following
|
13 |
# disclaimer in the documentation and/or other materials
|
14 |
# provided with the distribution.
|
15 |
#
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27 |
# POSSIBILITY OF SUCH DAMAGE.
|
28 |
#
|
29 |
# The views and conclusions contained in the software and
|
30 |
# documentation are those of the authors and should not be
|
31 |
# interpreted as representing official policies, either expressed
|
32 |
# or implied, of GRNET S.A.command
|
33 |
|
34 |
from kamaki.cli import command |
35 |
from kamaki.cli.command_tree import CommandTree |
36 |
from kamaki.cli.errors import raiseCLIError, CLISyntaxError |
37 |
from kamaki.cli.utils import ( |
38 |
format_size, |
39 |
print_dict, |
40 |
pretty_keys, |
41 |
page_hold, |
42 |
ask_user) |
43 |
from kamaki.cli.argument import FlagArgument, ValueArgument, IntArgument |
44 |
from kamaki.cli.argument import KeyValueArgument, DateArgument |
45 |
from kamaki.cli.argument import ProgressBarArgument |
46 |
from kamaki.cli.commands import _command_init |
47 |
from kamaki.clients.pithos import PithosClient, ClientError |
48 |
from kamaki.cli.utils import bold |
49 |
from sys import stdout |
50 |
from time import localtime, strftime |
51 |
from logging import getLogger |
52 |
from os import path |
53 |
|
54 |
kloger = getLogger('kamaki')
|
55 |
|
56 |
pithos_cmds = CommandTree('store', 'Pithos+ storage commands') |
57 |
_commands = [pithos_cmds] |
58 |
|
59 |
|
60 |
about_directories = [ |
61 |
'Kamaki hanldes directories the same way as OOS Storage and Pithos+:',
|
62 |
'A directory is an object with type "application/directory"',
|
63 |
'An object with path dir/name can exist even if dir does not exist or',
|
64 |
'even if dir is a non directory object. Users can modify dir without',
|
65 |
'affecting the dir/name object in any way.']
|
66 |
|
67 |
|
68 |
# Argument functionality
|
69 |
|
70 |
def raise_connection_errors(e): |
71 |
if e.status in range(200) + [403, 401]: |
72 |
raiseCLIError(e, details=[ |
73 |
'Please check the service url and the authentication information',
|
74 |
' ',
|
75 |
' to get the service url: /config get store.url',
|
76 |
' to set the service url: /config set store.url <url>',
|
77 |
' ',
|
78 |
' to get user the account: /config get store.account',
|
79 |
' or /config get account',
|
80 |
' to set the user account: /config set store.account <account>',
|
81 |
' ',
|
82 |
' to get authentication token: /config get token',
|
83 |
' to set authentication token: /config set token <token>'
|
84 |
]) |
85 |
elif e.status == 413: |
86 |
raiseCLIError(e, details=[ |
87 |
'Get quotas:',
|
88 |
'- total quota: /store quota',
|
89 |
'- container quota: /store quota <container>',
|
90 |
'Users shall set a higher container quota, if available:',
|
91 |
'- /store setquota <limit in KB> <container>'
|
92 |
]) |
93 |
|
94 |
|
95 |
def check_range(start, end): |
96 |
"""
|
97 |
:param start: (int)
|
98 |
|
99 |
:param end: (int)
|
100 |
|
101 |
:returns: (int(start), int(end))
|
102 |
|
103 |
:raises CLIError - Invalid start/end value in range
|
104 |
:raises CLIError - Invalid range
|
105 |
"""
|
106 |
try:
|
107 |
start = int(start)
|
108 |
except ValueError as e: |
109 |
raiseCLIError(e, 'Invalid start value %s in range' % start)
|
110 |
try:
|
111 |
end = int(end)
|
112 |
except ValueError as e: |
113 |
raiseCLIError(e, 'Invalid end value %s in range' % end)
|
114 |
if start > end:
|
115 |
raiseCLIError('Invalid range %s-%s' % (start, end))
|
116 |
return (start, end)
|
117 |
|
118 |
|
119 |
class DelimiterArgument(ValueArgument): |
120 |
"""
|
121 |
:value type: string
|
122 |
:value returns: given string or /
|
123 |
"""
|
124 |
|
125 |
def __init__(self, caller_obj, help='', parsed_name=None, default=None): |
126 |
super(DelimiterArgument, self).__init__(help, parsed_name, default) |
127 |
self.caller_obj = caller_obj
|
128 |
|
129 |
@property
|
130 |
def value(self): |
131 |
if self.caller_obj['recursive']: |
132 |
return '/' |
133 |
return getattr(self, '_value', self.default) |
134 |
|
135 |
@value.setter
|
136 |
def value(self, newvalue): |
137 |
self._value = newvalue
|
138 |
|
139 |
|
140 |
class SharingArgument(ValueArgument): |
141 |
"""Set sharing (read and/or write) groups
|
142 |
.
|
143 |
:value type: "read=term1,term2,... write=term1,term2,..."
|
144 |
.
|
145 |
:value returns: {'read':['term1', 'term2', ...],
|
146 |
. 'write':['term1', 'term2', ...]}
|
147 |
"""
|
148 |
|
149 |
@property
|
150 |
def value(self): |
151 |
return getattr(self, '_value', self.default) |
152 |
|
153 |
@value.setter
|
154 |
def value(self, newvalue): |
155 |
perms = {} |
156 |
try:
|
157 |
permlist = newvalue.split(' ')
|
158 |
except AttributeError: |
159 |
return
|
160 |
for p in permlist: |
161 |
try:
|
162 |
(key, val) = p.split('=')
|
163 |
except ValueError as err: |
164 |
raiseCLIError(err, 'Error in --sharing',
|
165 |
details='Incorrect format',
|
166 |
importance=1)
|
167 |
if key.lower() not in ('read', 'write'): |
168 |
raiseCLIError(err, 'Error in --sharing',
|
169 |
details='Invalid permission key %s' % key,
|
170 |
importance=1)
|
171 |
val_list = val.split(',')
|
172 |
if not key in perms: |
173 |
perms[key] = [] |
174 |
for item in val_list: |
175 |
if item not in perms[key]: |
176 |
perms[key].append(item) |
177 |
self._value = perms
|
178 |
|
179 |
|
180 |
class RangeArgument(ValueArgument): |
181 |
"""
|
182 |
:value type: string of the form <start>-<end> where <start> and <end> are
|
183 |
integers
|
184 |
:value returns: the input string, after type checking <start> and <end>
|
185 |
"""
|
186 |
|
187 |
@property
|
188 |
def value(self): |
189 |
return getattr(self, '_value', self.default) |
190 |
|
191 |
@value.setter
|
192 |
def value(self, newvalue): |
193 |
if newvalue is None: |
194 |
self._value = self.default |
195 |
return
|
196 |
(start, end) = newvalue.split('-')
|
197 |
(start, end) = (int(start), int(end)) |
198 |
self._value = '%s-%s' % (start, end) |
199 |
|
200 |
# Command specs
|
201 |
|
202 |
|
203 |
class _pithos_init(_command_init): |
204 |
"""Initialize a pithos+ kamaki client"""
|
205 |
|
206 |
def main(self): |
207 |
self.token = self.config.get('store', 'token')\ |
208 |
or self.config.get('global', 'token') |
209 |
self.base_url = self.config.get('store', 'url')\ |
210 |
or self.config.get('global', 'url') |
211 |
self.account = self.config.get('store', 'account')\ |
212 |
or self.config.get('global', 'account') |
213 |
self.container = self.config.get('store', 'container')\ |
214 |
or self.config.get('global', 'container') |
215 |
self.client = PithosClient(base_url=self.base_url, |
216 |
token=self.token,
|
217 |
account=self.account,
|
218 |
container=self.container)
|
219 |
|
220 |
|
221 |
class _store_account_command(_pithos_init): |
222 |
"""Base class for account level storage commands"""
|
223 |
|
224 |
def __init__(self, arguments={}): |
225 |
super(_store_account_command, self).__init__(arguments) |
226 |
self['account'] = ValueArgument( |
227 |
'Set user account (not permanent)',
|
228 |
'--account')
|
229 |
|
230 |
def main(self): |
231 |
super(_store_account_command, self).main() |
232 |
if self['account']: |
233 |
self.client.account = self['account'] |
234 |
|
235 |
|
236 |
class _store_container_command(_store_account_command): |
237 |
"""Base class for container level storage commands"""
|
238 |
|
239 |
generic_err_details = ['To specify a container:',
|
240 |
' 1. Set store.container variable (permanent)',
|
241 |
' /config set store.container <container>',
|
242 |
' 2. --container=<container> (temporary, overrides 1)',
|
243 |
' 3. Use the container:path format (temporary, overrides all)']
|
244 |
|
245 |
container = None
|
246 |
path = None
|
247 |
|
248 |
def __init__(self, arguments={}): |
249 |
super(_store_container_command, self).__init__(arguments) |
250 |
self['container'] = ValueArgument( |
251 |
'Set container to work with (temporary)',
|
252 |
'--container')
|
253 |
|
254 |
def extract_container_and_path(self, |
255 |
container_with_path, |
256 |
path_is_optional=True):
|
257 |
try:
|
258 |
assert isinstance(container_with_path, str) |
259 |
except AssertionError as err: |
260 |
raiseCLIError(err) |
261 |
|
262 |
cont, sep, path = container_with_path.partition(':')
|
263 |
|
264 |
if sep:
|
265 |
if not cont: |
266 |
raiseCLIError(CLISyntaxError('Container is missing\n',
|
267 |
details=self.generic_err_details))
|
268 |
alt_cont = self['container'] |
269 |
if alt_cont and cont != alt_cont: |
270 |
raiseCLIError(CLISyntaxError( |
271 |
'Conflict: 2 containers (%s, %s)' % (cont, alt_cont),
|
272 |
details=self.generic_err_details)
|
273 |
) |
274 |
self.container = cont
|
275 |
if not path: |
276 |
raiseCLIError(CLISyntaxError( |
277 |
'Path is missing for object in container %s' % cont,
|
278 |
details=self.generic_err_details)
|
279 |
) |
280 |
self.path = path
|
281 |
else:
|
282 |
alt_cont = self['container'] or self.client.container |
283 |
if alt_cont:
|
284 |
self.container = alt_cont
|
285 |
self.path = cont
|
286 |
elif path_is_optional:
|
287 |
self.container = cont
|
288 |
self.path = None |
289 |
else:
|
290 |
self.container = cont
|
291 |
raiseCLIError(CLISyntaxError( |
292 |
'Both container and path are required',
|
293 |
details=self.generic_err_details)
|
294 |
) |
295 |
|
296 |
def main(self, container_with_path=None, path_is_optional=True): |
297 |
super(_store_container_command, self).main() |
298 |
if container_with_path is not None: |
299 |
self.extract_container_and_path(
|
300 |
container_with_path, |
301 |
path_is_optional) |
302 |
self.client.container = self.container |
303 |
elif self['container']: |
304 |
self.client.container = self['container'] |
305 |
self.container = self.client.container |
306 |
|
307 |
|
308 |
@command(pithos_cmds)
|
309 |
class store_list(_store_container_command): |
310 |
"""List containers, object trees or objects in a directory
|
311 |
Use with:
|
312 |
1 no parameters : containers in set account
|
313 |
2. one parameter (container) or --container : contents of container
|
314 |
3. <container>:<prefix> or --container=<container> <prefix>: objects in
|
315 |
. container starting with prefix
|
316 |
"""
|
317 |
|
318 |
arguments = dict(
|
319 |
detail=FlagArgument('show detailed output', '-l'), |
320 |
limit=IntArgument('limit the number of listed items', '-n'), |
321 |
marker=ValueArgument('show output greater that marker', '--marker'), |
322 |
prefix=ValueArgument('show output starting with prefix', '--prefix'), |
323 |
delimiter=ValueArgument('show output up to delimiter', '--delimiter'), |
324 |
path=ValueArgument( |
325 |
'show output starting with prefix up to /',
|
326 |
'--path'),
|
327 |
meta=ValueArgument( |
328 |
'show output with specified meta keys',
|
329 |
'--meta',
|
330 |
default=[]), |
331 |
if_modified_since=ValueArgument( |
332 |
'show output modified since then',
|
333 |
'--if-modified-since'),
|
334 |
if_unmodified_since=ValueArgument( |
335 |
'show output not modified since then',
|
336 |
'--if-unmodified-since'),
|
337 |
until=DateArgument('show metadata until then', '--until'), |
338 |
format=ValueArgument( |
339 |
'format to parse until data (default: d/m/Y H:M:S )',
|
340 |
'--format'),
|
341 |
shared=FlagArgument('show only shared', '--shared'), |
342 |
public=FlagArgument('show only public', '--public'), |
343 |
more=FlagArgument( |
344 |
'output results in pages (-n to set items per page, default 10)',
|
345 |
'--more'),
|
346 |
exact_match=FlagArgument( |
347 |
'Show only objects that match exactly with path',
|
348 |
'--exact-match')
|
349 |
) |
350 |
|
351 |
def print_objects(self, object_list): |
352 |
limit = int(self['limit']) if self['limit'] > 0 else len(object_list) |
353 |
for index, obj in enumerate(object_list): |
354 |
if (self['exact_match'] and self.path and\ |
355 |
obj['name'] != self.path) or 'content_type' not in obj: |
356 |
continue
|
357 |
pretty_obj = obj.copy() |
358 |
index += 1
|
359 |
empty_space = ' ' * (len(str(len(object_list))) - len(str(index))) |
360 |
if obj['content_type'] == 'application/directory': |
361 |
isDir = True
|
362 |
size = 'D'
|
363 |
else:
|
364 |
isDir = False
|
365 |
size = format_size(obj['bytes'])
|
366 |
pretty_obj['bytes'] = '%s (%s)' % (obj['bytes'], size) |
367 |
oname = bold(obj['name'])
|
368 |
if self['detail']: |
369 |
print('%s%s. %s' % (empty_space, index, oname))
|
370 |
print_dict(pretty_keys(pretty_obj), exclude=('name'))
|
371 |
print
|
372 |
else:
|
373 |
oname = '%s%s. %6s %s' % (empty_space, index, size, oname)
|
374 |
oname += '/' if isDir else '' |
375 |
print(oname) |
376 |
if self['more']: |
377 |
page_hold(index, limit, len(object_list))
|
378 |
|
379 |
def print_containers(self, container_list): |
380 |
limit = int(self['limit']) if self['limit'] > 0\ |
381 |
else len(container_list) |
382 |
for index, container in enumerate(container_list): |
383 |
if 'bytes' in container: |
384 |
size = format_size(container['bytes'])
|
385 |
cname = '%s. %s' % (index + 1, bold(container['name'])) |
386 |
if self['detail']: |
387 |
print(cname) |
388 |
pretty_c = container.copy() |
389 |
if 'bytes' in container: |
390 |
pretty_c['bytes'] = '%s (%s)' % (container['bytes'], size) |
391 |
print_dict(pretty_keys(pretty_c), exclude=('name'))
|
392 |
print
|
393 |
else:
|
394 |
if 'count' in container and 'bytes' in container: |
395 |
print('%s (%s, %s objects)'\
|
396 |
% (cname, size, container['count']))
|
397 |
else:
|
398 |
print(cname) |
399 |
if self['more']: |
400 |
page_hold(index + 1, limit, len(container_list)) |
401 |
|
402 |
def main(self, container____path__=None): |
403 |
super(self.__class__, self).main(container____path__) |
404 |
try:
|
405 |
if self.container is None: |
406 |
r = self.client.account_get(
|
407 |
limit=False if self['more'] else self['limit'], |
408 |
marker=self['marker'], |
409 |
if_modified_since=self['if_modified_since'], |
410 |
if_unmodified_since=self['if_unmodified_since'], |
411 |
until=self['until'], |
412 |
show_only_shared=self['shared']) |
413 |
self.print_containers(r.json)
|
414 |
else:
|
415 |
prefix = self.path if self.path else self['prefix'] |
416 |
r = self.client.container_get(
|
417 |
limit=False if self['more'] else self['limit'], |
418 |
marker=self['marker'], |
419 |
prefix=prefix, |
420 |
delimiter=self['delimiter'], |
421 |
path=self['path'], |
422 |
if_modified_since=self['if_modified_since'], |
423 |
if_unmodified_since=self['if_unmodified_since'], |
424 |
until=self['until'], |
425 |
meta=self['meta'], |
426 |
show_only_shared=self['shared']) |
427 |
self.print_objects(r.json)
|
428 |
except ClientError as err: |
429 |
if err.status == 404: |
430 |
if 'container' in ('%s' % err).lower(): |
431 |
raiseCLIError( |
432 |
err, |
433 |
'No container %s in account %s'\
|
434 |
% (self.container, self.account), |
435 |
details=self.generic_err_details)
|
436 |
elif 'object' in ('%s' % err).lower(): |
437 |
raiseCLIError( |
438 |
err, |
439 |
'No object %s in %s\'s container %s'\
|
440 |
% (self.path, self.account, self.container), |
441 |
details=self.generic_err_details)
|
442 |
raise_connection_errors(err) |
443 |
raiseCLIError(err) |
444 |
except Exception as e: |
445 |
raiseCLIError(e) |
446 |
|
447 |
|
448 |
@command(pithos_cmds)
|
449 |
class store_mkdir(_store_container_command): |
450 |
"""Create a directory"""
|
451 |
|
452 |
__doc__ += '\n. '.join(about_directories)
|
453 |
|
454 |
def main(self, container___directory): |
455 |
super(self.__class__, |
456 |
self).main(container___directory, path_is_optional=False) |
457 |
try:
|
458 |
self.client.create_directory(self.path) |
459 |
except ClientError as err: |
460 |
if err.status == 404: |
461 |
if 'container' in ('%s' % err).lower(): |
462 |
raiseCLIError( |
463 |
err, |
464 |
'No container %s in account %s'\
|
465 |
% (self.container, self.account), |
466 |
details=self.generic_err_details)
|
467 |
elif 'object' in ('%s' % err).lower(): |
468 |
raiseCLIError( |
469 |
err, |
470 |
'No object %s in container %s'\
|
471 |
% (self.path, self.container), |
472 |
details=self.generic_err_details)
|
473 |
raise_connection_errors(err) |
474 |
raiseCLIError(err) |
475 |
except Exception as err: |
476 |
raiseCLIError(err) |
477 |
|
478 |
|
479 |
@command(pithos_cmds)
|
480 |
class store_touch(_store_container_command): |
481 |
"""Create an empty object (file)
|
482 |
If object exists, this command will reset it to 0 length
|
483 |
"""
|
484 |
|
485 |
arguments = dict(
|
486 |
content_type=ValueArgument( |
487 |
'Set content type (default: application/octet-stream)',
|
488 |
'--content-type',
|
489 |
default='application/octet-stream')
|
490 |
) |
491 |
|
492 |
def main(self, container___path): |
493 |
super(store_touch, self).main(container___path) |
494 |
try:
|
495 |
self.client.create_object(self.path, self['content_type']) |
496 |
except ClientError as err: |
497 |
if err.status == 404: |
498 |
if 'container' in ('%s' % err).lower(): |
499 |
raiseCLIError( |
500 |
err, |
501 |
'No container %s in account %s'\
|
502 |
% (self.container, self.account), |
503 |
details=self.generic_err_details)
|
504 |
elif 'object' in ('%s' % err).lower(): |
505 |
raiseCLIError( |
506 |
err, |
507 |
'No object %s in container %s'\
|
508 |
% (self.path, self.container), |
509 |
details=self.generic_err_details)
|
510 |
raise_connection_errors(err) |
511 |
raiseCLIError(err) |
512 |
except Exception as err: |
513 |
raiseCLIError(err) |
514 |
|
515 |
|
516 |
@command(pithos_cmds)
|
517 |
class store_create(_store_container_command): |
518 |
"""Create a container"""
|
519 |
|
520 |
arguments = dict(
|
521 |
versioning=ValueArgument( |
522 |
'set container versioning (auto/none)',
|
523 |
'--versioning'),
|
524 |
quota=IntArgument('set default container quota', '--quota'), |
525 |
meta=KeyValueArgument( |
526 |
'set container metadata (can be repeated)',
|
527 |
'--meta')
|
528 |
) |
529 |
|
530 |
def main(self, container): |
531 |
super(self.__class__, self).main(container) |
532 |
try:
|
533 |
self.client.container_put(quota=self['quota'], |
534 |
versioning=self['versioning'], |
535 |
metadata=self['meta']) |
536 |
except ClientError as err: |
537 |
if err.status == 404: |
538 |
if 'container' in ('%s' % err).lower(): |
539 |
raiseCLIError( |
540 |
err, |
541 |
'No container %s in account %s'\
|
542 |
% (self.container, self.account), |
543 |
details=self.generic_err_details)
|
544 |
elif 'object' in ('%s' % err).lower(): |
545 |
raiseCLIError( |
546 |
err, |
547 |
'No object %s in container %s'\
|
548 |
% (self.path, self.container), |
549 |
details=self.generic_err_details)
|
550 |
raise_connection_errors(err) |
551 |
raiseCLIError(err) |
552 |
except Exception as e: |
553 |
raiseCLIError(e) |
554 |
|
555 |
|
556 |
@command(pithos_cmds)
|
557 |
class store_copy(_store_container_command): |
558 |
"""Copy objects from container to (another) container
|
559 |
Semantics:
|
560 |
copy cont:path path2
|
561 |
. will copy all <obj> prefixed with path, as path2<obj>
|
562 |
. or as path2 if path corresponds to just one whole object
|
563 |
copy cont:path cont2:
|
564 |
. will copy all <obj> prefixed with path to container cont2
|
565 |
copy cont:path [cont2:]path2 --exact-match
|
566 |
. will copy at most one <obj> as a new object named path2,
|
567 |
. provided path corresponds to a whole object path
|
568 |
copy cont:path [cont2:]path2 --replace
|
569 |
. will copy all <obj> prefixed with path, replacing path with path2
|
570 |
where <obj> is a full file or directory object path.
|
571 |
Use options:
|
572 |
1. <container1>:<path1> [container2:]<path2> : if container2 is not given,
|
573 |
destination is container1:path2
|
574 |
2. <container>:<path1> <path2> : make a copy in the same container
|
575 |
3. Can use --container= instead of <container1>
|
576 |
"""
|
577 |
|
578 |
arguments = dict(
|
579 |
source_version=ValueArgument( |
580 |
'copy specific version',
|
581 |
'--source-version'),
|
582 |
public=ValueArgument('make object publicly accessible', '--public'), |
583 |
content_type=ValueArgument( |
584 |
'change object\'s content type',
|
585 |
'--content-type'),
|
586 |
recursive=FlagArgument( |
587 |
'mass copy with delimiter /',
|
588 |
('-r', '--recursive')), |
589 |
exact_match=FlagArgument( |
590 |
'Copy only the object that fully matches path',
|
591 |
'--exact-match'),
|
592 |
replace=FlagArgument('Replace src. path with dst. path', '--replace') |
593 |
) |
594 |
|
595 |
def _objlist(self, dst_path): |
596 |
if self['exact_match']: |
597 |
return [(dst_path if dst_path else self.path, self.path)] |
598 |
r = self.client.container_get(prefix=self.path) |
599 |
if len(r.json) == 1: |
600 |
obj = r.json[0]
|
601 |
return [(obj['name'], dst_path if dst_path else obj['name'])] |
602 |
return [(obj['name'], '%s%s' % ( |
603 |
dst_path, |
604 |
obj['name'][len(self.path) if self['replace'] else 0:]) |
605 |
) for obj in r.json] |
606 |
|
607 |
def main(self, source_container___path, destination_container___path): |
608 |
super(self.__class__, |
609 |
self).main(source_container___path, path_is_optional=False) |
610 |
try:
|
611 |
dst = destination_container___path.split(':')
|
612 |
(dst_cont, dst_path) = (dst[0], dst[1])\ |
613 |
if len(dst) > 1 else (None, dst[0]) |
614 |
for src_object, dst_object in self._objlist(dst_path): |
615 |
self.client.copy_object(src_container=self.container, |
616 |
src_object=src_object, |
617 |
dst_container=dst_cont if dst_cont else self.container, |
618 |
dst_object=dst_object, |
619 |
source_version=self['source_version'], |
620 |
public=self['public'], |
621 |
content_type=self['content_type']) |
622 |
except ClientError as err: |
623 |
if err.status == 404: |
624 |
if 'object' in ('%s' % err).lower(): |
625 |
raiseCLIError( |
626 |
err, |
627 |
'No object %s in container %s'\
|
628 |
% (self.path, self.container), |
629 |
details=self.generic_err_details)
|
630 |
elif 'container' in ('%s' % err).lower(): |
631 |
cont_msg = '(either %s or %s)' % (
|
632 |
self.container,
|
633 |
dst_cont |
634 |
) if dst_cont else self.container |
635 |
raiseCLIError( |
636 |
err, |
637 |
'No container %s in account %s'\
|
638 |
% (cont_msg, self.account),
|
639 |
details=self.generic_err_details)
|
640 |
raise_connection_errors(err) |
641 |
raiseCLIError(err) |
642 |
except Exception as e: |
643 |
raiseCLIError(e) |
644 |
|
645 |
|
646 |
@command(pithos_cmds)
|
647 |
class store_move(_store_container_command): |
648 |
"""Move/rename objects
|
649 |
Semantics:
|
650 |
move cont:path path2
|
651 |
. will move all <obj> prefixed with path, as path2<obj>
|
652 |
. or as path2 if path corresponds to just one whole object
|
653 |
move cont:path cont2:
|
654 |
. will move all <obj> prefixed with path to container cont2
|
655 |
move cont:path [cont2:]path2 --exact-match
|
656 |
. will move at most one <obj> as a new object named path2,
|
657 |
. provided path corresponds to a whole object path
|
658 |
move cont:path [cont2:]path2 --replace
|
659 |
. will move all <obj> prefixed with path, replacing path with path2
|
660 |
where <obj> is a full file or directory object path.
|
661 |
Use options:
|
662 |
1. <container1>:<path1> [container2:]<path2> : if container2 not given,
|
663 |
destination is container1:path2
|
664 |
2. <container>:<path1> path2 : rename
|
665 |
3. Can use --container= instead of <container1>
|
666 |
"""
|
667 |
|
668 |
arguments = dict(
|
669 |
source_version=ValueArgument('specify version', '--source-version'), |
670 |
public=FlagArgument('make object publicly accessible', '--public'), |
671 |
content_type=ValueArgument('modify content type', '--content-type'), |
672 |
recursive=FlagArgument('up to delimiter /', ('-r', '--recursive')), |
673 |
exact_match=FlagArgument( |
674 |
'Copy only the object that fully matches path',
|
675 |
'--exact-match'),
|
676 |
replace=FlagArgument('Replace src. path with dst. path', '--replace') |
677 |
) |
678 |
|
679 |
def _objlist(self, dst_path): |
680 |
if self['exact_match']: |
681 |
return [(dst_path if dst_path else self.path, self.path)] |
682 |
r = self.client.container_get(prefix=self.path) |
683 |
if len(r.json) == 1: |
684 |
obj = r.json[0]
|
685 |
return [(obj['name'], dst_path if dst_path else obj['name'])] |
686 |
return [(obj['name'], '%s%s' % ( |
687 |
dst_path, |
688 |
obj['name'][len(self.path) if self['replace'] else 0:]) |
689 |
) for obj in r.json] |
690 |
|
691 |
def main(self, source_container___path, destination_container____path__): |
692 |
super(self.__class__, |
693 |
self).main(source_container___path, path_is_optional=False) |
694 |
try:
|
695 |
dst = destination_container____path__.split(':')
|
696 |
(dst_cont, dst_path) = (dst[0], dst[1])\ |
697 |
if len(dst) > 1 else (None, dst[0]) |
698 |
for src_object, dst_object in self._objlist(dst_path): |
699 |
self.client.move_object(
|
700 |
src_container=self.container,
|
701 |
src_object=src_object, |
702 |
dst_container=dst_cont if dst_cont else self.container, |
703 |
dst_object=dst_object, |
704 |
source_version=self['source_version'], |
705 |
public=self['public'], |
706 |
content_type=self['content_type']) |
707 |
except ClientError as err: |
708 |
if err.status == 404: |
709 |
if 'object' in ('%s' % err).lower(): |
710 |
raiseCLIError( |
711 |
err, |
712 |
'No object %s in container %s'\
|
713 |
% (self.path, self.container), |
714 |
details=self.generic_err_details)
|
715 |
elif 'container' in ('%s' % err).lower(): |
716 |
cont_msg = '(either %s or %s)' % (
|
717 |
self.container,
|
718 |
dst_cont |
719 |
) if dst_cont else self.container |
720 |
raiseCLIError( |
721 |
err, |
722 |
'No container %s in account %s'\
|
723 |
% (cont_msg, self.account),
|
724 |
details=self.generic_err_details)
|
725 |
raise_connection_errors(err) |
726 |
raiseCLIError(err) |
727 |
except Exception as e: |
728 |
raiseCLIError(e) |
729 |
|
730 |
|
731 |
@command(pithos_cmds)
|
732 |
class store_append(_store_container_command): |
733 |
"""Append local file to (existing) remote object
|
734 |
The remote object should exist.
|
735 |
If the remote object is a directory, it is transformed into a file.
|
736 |
In the later case, objects under the directory remain intact.
|
737 |
"""
|
738 |
|
739 |
arguments = dict(
|
740 |
progress_bar=ProgressBarArgument( |
741 |
'do not show progress bar',
|
742 |
'--no-progress-bar',
|
743 |
default=False)
|
744 |
) |
745 |
|
746 |
def main(self, local_path, container___path): |
747 |
super(self.__class__, self).main( |
748 |
container___path, |
749 |
path_is_optional=False)
|
750 |
progress_bar = self.arguments['progress_bar'] |
751 |
try:
|
752 |
upload_cb = progress_bar.get_generator('Appending blocks')
|
753 |
except Exception: |
754 |
upload_cb = None
|
755 |
try:
|
756 |
f = open(local_path, 'rb') |
757 |
self.client.append_object(self.path, f, upload_cb) |
758 |
except ClientError as err: |
759 |
progress_bar.finish() |
760 |
if err.status == 404: |
761 |
if 'container' in ('%s' % err).lower(): |
762 |
raiseCLIError( |
763 |
err, |
764 |
'No container %s in account %s'\
|
765 |
% (self.container, self.account), |
766 |
details=self.generic_err_details)
|
767 |
elif 'object' in ('%s' % err).lower(): |
768 |
raiseCLIError( |
769 |
err, |
770 |
'No object %s in container %s'\
|
771 |
% (self.path, self.container), |
772 |
details=self.generic_err_details)
|
773 |
raise_connection_errors(err) |
774 |
raiseCLIError(err) |
775 |
except Exception as e: |
776 |
progress_bar.finish() |
777 |
raiseCLIError(e) |
778 |
finally:
|
779 |
progress_bar.finish() |
780 |
|
781 |
|
782 |
@command(pithos_cmds)
|
783 |
class store_truncate(_store_container_command): |
784 |
"""Truncate remote file up to a size (default is 0)"""
|
785 |
|
786 |
def main(self, container___path, size=0): |
787 |
super(self.__class__, self).main(container___path) |
788 |
try:
|
789 |
self.client.truncate_object(self.path, size) |
790 |
except ClientError as err: |
791 |
if err.status == 404: |
792 |
if 'container' in ('%s' % err).lower(): |
793 |
raiseCLIError( |
794 |
err, |
795 |
'No container %s in account %s'\
|
796 |
% (self.container, self.account), |
797 |
details=self.generic_err_details)
|
798 |
elif 'object' in ('%s' % err).lower(): |
799 |
raiseCLIError( |
800 |
err, |
801 |
'No object %s in container %s'\
|
802 |
% (self.path, self.container), |
803 |
details=self.generic_err_details)
|
804 |
if err.status == 400 and\ |
805 |
'object length is smaller than range length'\
|
806 |
in ('%s' % err).lower(): |
807 |
raiseCLIError(err, 'Object %s:%s <= %sb' % (
|
808 |
self.container,
|
809 |
self.path,
|
810 |
size)) |
811 |
raise_connection_errors(err) |
812 |
raiseCLIError(err) |
813 |
except Exception as e: |
814 |
raiseCLIError(e) |
815 |
|
816 |
|
817 |
@command(pithos_cmds)
|
818 |
class store_overwrite(_store_container_command): |
819 |
"""Overwrite part (from start to end) of a remote file
|
820 |
overwrite local-path container 10 20
|
821 |
. will overwrite bytes from 10 to 20 of a remote file with the same name
|
822 |
. as local-path basename
|
823 |
overwrite local-path container:path 10 20
|
824 |
. will overwrite as above, but the remote file is named path
|
825 |
"""
|
826 |
|
827 |
arguments = dict(
|
828 |
progress_bar=ProgressBarArgument( |
829 |
'do not show progress bar',
|
830 |
'--no-progress-bar',
|
831 |
default=False)
|
832 |
) |
833 |
|
834 |
def main(self, local_path, container____path__, start, end): |
835 |
(start, end) = check_range(start, end) |
836 |
super(self.__class__, self).main(container____path__) |
837 |
try:
|
838 |
f = open(local_path, 'rb') |
839 |
f.seek(0, 2) |
840 |
f_size = f.tell() |
841 |
f.seek(start, 0)
|
842 |
except Exception as e: |
843 |
raiseCLIError(e) |
844 |
progress_bar = self.arguments['progress_bar'] |
845 |
try:
|
846 |
upload_cb = progress_bar.get_generator( |
847 |
'Overwriting %s blocks' % end - start)
|
848 |
except Exception: |
849 |
upload_cb = None
|
850 |
try:
|
851 |
self.path = self.path if self.path else path.basename(local_path) |
852 |
self.client.overwrite_object(
|
853 |
obj=self.path,
|
854 |
start=start, |
855 |
end=end, |
856 |
source_file=f, |
857 |
upload_cb=upload_cb) |
858 |
except ClientError as err: |
859 |
progress_bar.finish() |
860 |
if (err.status == 400 and\ |
861 |
'content length does not match range' in ('%s' % err).lower()) or\ |
862 |
err.status == 416:
|
863 |
raiseCLIError(err, details=[ |
864 |
'Content size: %s' % f_size,
|
865 |
'Range: %s-%s (size: %s)' % (start, end, end - start)])
|
866 |
elif err.status == 404: |
867 |
if 'container' in ('%s' % err).lower(): |
868 |
raiseCLIError( |
869 |
err, |
870 |
'No container %s in account %s'\
|
871 |
% (self.container, self.account), |
872 |
details=self.generic_err_details)
|
873 |
elif 'object' in ('%s' % err).lower(): |
874 |
raiseCLIError( |
875 |
err, |
876 |
'No object %s in container %s'\
|
877 |
% (self.path, self.container), |
878 |
details=self.generic_err_details)
|
879 |
raise_connection_errors(err) |
880 |
raiseCLIError(err) |
881 |
except Exception as e: |
882 |
progress_bar.finish() |
883 |
raiseCLIError(e) |
884 |
finally:
|
885 |
progress_bar.finish() |
886 |
|
887 |
|
888 |
@command(pithos_cmds)
|
889 |
class store_manifest(_store_container_command): |
890 |
"""Create a remote file of uploaded parts by manifestation
|
891 |
Remains functional for compatibility with OOS Storage. Users are advised
|
892 |
to use the upload command instead.
|
893 |
Manifestation is a compliant process for uploading large files. The files
|
894 |
have to be chunked in smalled files and uploaded as <prefix><increment>
|
895 |
where increment is 1, 2, ...
|
896 |
Finally, the manifest command glues partial files together in one file
|
897 |
named <prefix>
|
898 |
The upload command is faster, easier and more intuitive than manifest
|
899 |
"""
|
900 |
|
901 |
arguments = dict(
|
902 |
etag=ValueArgument('check written data', '--etag'), |
903 |
content_encoding=ValueArgument( |
904 |
'set MIME content type',
|
905 |
'--content-encoding'),
|
906 |
content_disposition=ValueArgument( |
907 |
'the presentation style of the object',
|
908 |
'--content-disposition'),
|
909 |
content_type=ValueArgument( |
910 |
'specify content type',
|
911 |
'--content-type',
|
912 |
default='application/octet-stream'),
|
913 |
sharing=SharingArgument( |
914 |
'define object sharing policy \n' +\
|
915 |
' ( "read=user1,grp1,user2,... write=user1,grp2,..." )',
|
916 |
'--sharing'),
|
917 |
public=FlagArgument('make object publicly accessible', '--public') |
918 |
) |
919 |
|
920 |
def main(self, container___path): |
921 |
super(self.__class__, |
922 |
self).main(container___path, path_is_optional=False) |
923 |
try:
|
924 |
self.client.create_object_by_manifestation(
|
925 |
self.path,
|
926 |
content_encoding=self['content_encoding'], |
927 |
content_disposition=self['content_disposition'], |
928 |
content_type=self['content_type'], |
929 |
sharing=self['sharing'], |
930 |
public=self['public']) |
931 |
except ClientError as err: |
932 |
if err.status == 404: |
933 |
if 'container' in ('%s' % err).lower(): |
934 |
raiseCLIError( |
935 |
err, |
936 |
'No container %s in account %s'\
|
937 |
% (self.container, self.account), |
938 |
details=self.generic_err_details)
|
939 |
elif 'object' in ('%s' % err).lower(): |
940 |
raiseCLIError( |
941 |
err, |
942 |
'No object %s in container %s'\
|
943 |
% (self.path, self.container), |
944 |
details=self.generic_err_details)
|
945 |
raise_connection_errors(err) |
946 |
raiseCLIError(err) |
947 |
except Exception as e: |
948 |
raiseCLIError(e) |
949 |
|
950 |
|
951 |
@command(pithos_cmds)
|
952 |
class store_upload(_store_container_command): |
953 |
"""Upload a file"""
|
954 |
|
955 |
arguments = dict(
|
956 |
use_hashes=FlagArgument( |
957 |
'provide hashmap file instead of data',
|
958 |
'--use-hashes'),
|
959 |
etag=ValueArgument('check written data', '--etag'), |
960 |
unchunked=FlagArgument('avoid chunked transfer mode', '--unchunked'), |
961 |
content_encoding=ValueArgument( |
962 |
'set MIME content type',
|
963 |
'--content-encoding'),
|
964 |
content_disposition=ValueArgument( |
965 |
'specify objects presentation style',
|
966 |
'--content-disposition'),
|
967 |
content_type=ValueArgument('specify content type', '--content-type'), |
968 |
sharing=SharingArgument( |
969 |
help='define sharing object policy \n' +\
|
970 |
'( "read=user1,grp1,user2,... write=user1,grp2,... )',
|
971 |
parsed_name='--sharing'),
|
972 |
public=FlagArgument('make object publicly accessible', '--public'), |
973 |
poolsize=IntArgument('set pool size', '--with-pool-size'), |
974 |
progress_bar=ProgressBarArgument( |
975 |
'do not show progress bar',
|
976 |
'--no-progress-bar',
|
977 |
default=False),
|
978 |
overwrite=FlagArgument('Force overwrite, if object exists', '-f') |
979 |
) |
980 |
|
981 |
def _remote_path(self, remote_path, local_path=''): |
982 |
if self['overwrite']: |
983 |
return remote_path
|
984 |
try:
|
985 |
r = self.client.get_object_info(remote_path)
|
986 |
except ClientError as ce: |
987 |
if ce.status == 404: |
988 |
return remote_path
|
989 |
raise ce
|
990 |
ctype = r.get('content-type', '') |
991 |
if 'application/directory' == ctype.lower(): |
992 |
ret = '%s/%s' % (remote_path, local_path)
|
993 |
return self._remote_path(ret) if local_path else ret |
994 |
raiseCLIError( |
995 |
'Object %s already exists' % remote_path,
|
996 |
importance=1,
|
997 |
details=['use -f to overwrite or resume'])
|
998 |
|
999 |
def main(self, local_path, container____path__=None): |
1000 |
super(self.__class__, self).main(container____path__) |
1001 |
remote_path = self.path if self.path else path.basename(local_path) |
1002 |
poolsize = self['poolsize'] |
1003 |
if poolsize > 0: |
1004 |
self.client.POOL_SIZE = int(poolsize) |
1005 |
params = dict(
|
1006 |
content_encoding=self['content_encoding'], |
1007 |
content_type=self['content_type'], |
1008 |
content_disposition=self['content_disposition'], |
1009 |
sharing=self['sharing'], |
1010 |
public=self['public']) |
1011 |
try:
|
1012 |
progress_bar = self.arguments['progress_bar'] |
1013 |
hash_bar = progress_bar.clone() |
1014 |
remote_path = self._remote_path(remote_path, local_path)
|
1015 |
with open(path.abspath(local_path), 'rb') as f: |
1016 |
if self['unchunked']: |
1017 |
self.client.upload_object_unchunked(
|
1018 |
remote_path, |
1019 |
f, |
1020 |
etag=self['etag'], |
1021 |
withHashFile=self['use_hashes'], |
1022 |
**params) |
1023 |
else:
|
1024 |
hash_cb = hash_bar.get_generator( |
1025 |
'Calculating block hashes')
|
1026 |
upload_cb = progress_bar.get_generator('Uploading')
|
1027 |
self.client.upload_object(
|
1028 |
remote_path, |
1029 |
f, |
1030 |
hash_cb=hash_cb, |
1031 |
upload_cb=upload_cb, |
1032 |
**params) |
1033 |
progress_bar.finish() |
1034 |
hash_bar.finish() |
1035 |
except ClientError as err: |
1036 |
try:
|
1037 |
progress_bar.finish() |
1038 |
hash_bar.finish() |
1039 |
except Exception: |
1040 |
pass
|
1041 |
if err.status == 404: |
1042 |
if 'container' in ('%s' % err).lower(): |
1043 |
raiseCLIError( |
1044 |
err, |
1045 |
'No container %s in account %s'\
|
1046 |
% (self.container, self.account), |
1047 |
details=[self.generic_err_details])
|
1048 |
elif err.status == 800: |
1049 |
raiseCLIError(err, details=[ |
1050 |
'Possible cause: temporary server failure',
|
1051 |
'Try to re-upload the file',
|
1052 |
'For more error details, try kamaki store upload -d'])
|
1053 |
raise_connection_errors(err) |
1054 |
raiseCLIError(err, 'Failed to upload to %s' % container____path__)
|
1055 |
except IOError as err: |
1056 |
try:
|
1057 |
progress_bar.finish() |
1058 |
hash_bar.finish() |
1059 |
except Exception: |
1060 |
pass
|
1061 |
raiseCLIError(err, 'Failed to read form file %s' % local_path, 2) |
1062 |
except Exception as e: |
1063 |
try:
|
1064 |
progress_bar.finish() |
1065 |
hash_bar.finish() |
1066 |
except Exception: |
1067 |
pass
|
1068 |
raiseCLIError(e) |
1069 |
print 'Upload completed' |
1070 |
|
1071 |
|
1072 |
@command(pithos_cmds)
|
1073 |
class store_cat(_store_container_command): |
1074 |
"""Print remote file contents to console"""
|
1075 |
|
1076 |
arguments = dict(
|
1077 |
range=RangeArgument('show range of data', '--range'), |
1078 |
if_match=ValueArgument('show output if ETags match', '--if-match'), |
1079 |
if_none_match=ValueArgument( |
1080 |
'show output if ETags match',
|
1081 |
'--if-none-match'),
|
1082 |
if_modified_since=DateArgument( |
1083 |
'show output modified since then',
|
1084 |
'--if-modified-since'),
|
1085 |
if_unmodified_since=DateArgument( |
1086 |
'show output unmodified since then',
|
1087 |
'--if-unmodified-since'),
|
1088 |
object_version=ValueArgument( |
1089 |
'get the specific version',
|
1090 |
'--object-version')
|
1091 |
) |
1092 |
|
1093 |
def main(self, container___path): |
1094 |
super(self.__class__, self).main( |
1095 |
container___path, |
1096 |
path_is_optional=False)
|
1097 |
try:
|
1098 |
self.client.download_object(self.path, stdout, |
1099 |
range=self['range'], |
1100 |
version=self['object_version'], |
1101 |
if_match=self['if_match'], |
1102 |
if_none_match=self['if_none_match'], |
1103 |
if_modified_since=self['if_modified_since'], |
1104 |
if_unmodified_since=self['if_unmodified_since']) |
1105 |
except ClientError as err: |
1106 |
if err.status == 404: |
1107 |
if 'container' in ('%s' % err).lower(): |
1108 |
raiseCLIError( |
1109 |
err, |
1110 |
'No container %s in account %s'\
|
1111 |
% (self.container, self.account), |
1112 |
details=self.generic_err_details)
|
1113 |
elif 'object' in ('%s' % err).lower(): |
1114 |
raiseCLIError( |
1115 |
err, |
1116 |
'No object %s in container %s'\
|
1117 |
% (self.path, self.container), |
1118 |
details=self.generic_err_details)
|
1119 |
raise_connection_errors(err) |
1120 |
raiseCLIError(err) |
1121 |
except Exception as e: |
1122 |
raiseCLIError(e) |
1123 |
|
1124 |
|
1125 |
@command(pithos_cmds)
|
1126 |
class store_download(_store_container_command): |
1127 |
"""Download remote object as local file"""
|
1128 |
|
1129 |
arguments = dict(
|
1130 |
resume=FlagArgument('Resume instead of overwrite', '--resume'), |
1131 |
range=RangeArgument('show range of data', '--range'), |
1132 |
if_match=ValueArgument('show output if ETags match', '--if-match'), |
1133 |
if_none_match=ValueArgument( |
1134 |
'show output if ETags match',
|
1135 |
'--if-none-match'),
|
1136 |
if_modified_since=DateArgument( |
1137 |
'show output modified since then',
|
1138 |
'--if-modified-since'),
|
1139 |
if_unmodified_since=DateArgument( |
1140 |
'show output unmodified since then',
|
1141 |
'--if-unmodified-since'),
|
1142 |
object_version=ValueArgument( |
1143 |
'get the specific version',
|
1144 |
'--object-version'),
|
1145 |
poolsize=IntArgument('set pool size', '--with-pool-size'), |
1146 |
progress_bar=ProgressBarArgument( |
1147 |
'do not show progress bar',
|
1148 |
'--no-progress-bar',
|
1149 |
default=False)
|
1150 |
) |
1151 |
|
1152 |
def _output_stream(self, local_path): |
1153 |
if local_path is None: |
1154 |
return stdout
|
1155 |
try:
|
1156 |
return open( |
1157 |
path.abspath(local_path), |
1158 |
'rwb+' if self['resume'] else 'wb+') |
1159 |
except IOError as err: |
1160 |
raiseCLIError(err, 'Cannot write to file %s' % local_path, 1) |
1161 |
|
1162 |
def main(self, container___path, local_path=None): |
1163 |
super(self.__class__, self).main( |
1164 |
container___path, |
1165 |
path_is_optional=False)
|
1166 |
|
1167 |
out = self._output_stream(local_path)
|
1168 |
poolsize = self['poolsize'] |
1169 |
if poolsize is not None: |
1170 |
self.client.POOL_SIZE = int(poolsize) |
1171 |
|
1172 |
try:
|
1173 |
progress_bar = self.arguments['progress_bar'] |
1174 |
download_cb = progress_bar.get_generator('Downloading')
|
1175 |
self.client.download_object(
|
1176 |
self.path,
|
1177 |
out, |
1178 |
download_cb=download_cb, |
1179 |
range=self['range'], |
1180 |
version=self['object_version'], |
1181 |
if_match=self['if_match'], |
1182 |
resume=self['resume'], |
1183 |
if_none_match=self['if_none_match'], |
1184 |
if_modified_since=self['if_modified_since'], |
1185 |
if_unmodified_since=self['if_unmodified_since']) |
1186 |
progress_bar.finish() |
1187 |
except ClientError as err: |
1188 |
progress_bar.finish() |
1189 |
if err.status == 404: |
1190 |
if 'container' in ('%s' % err).lower(): |
1191 |
raiseCLIError( |
1192 |
err, |
1193 |
'No container %s in account %s'\
|
1194 |
% (self.container, self.account), |
1195 |
details=self.generic_err_details)
|
1196 |
elif 'object' in ('%s' % err).lower(): |
1197 |
raiseCLIError( |
1198 |
err, |
1199 |
'No object %s in container %s'\
|
1200 |
% (self.path, self.container), |
1201 |
details=self.generic_err_details)
|
1202 |
raise_connection_errors(err) |
1203 |
raiseCLIError(err, '"%s" not accessible' % container___path)
|
1204 |
except IOError as err: |
1205 |
try:
|
1206 |
progress_bar.finish() |
1207 |
except Exception: |
1208 |
pass
|
1209 |
raiseCLIError(err, 'Failed to write on file %s' % local_path, 2) |
1210 |
except KeyboardInterrupt: |
1211 |
from threading import enumerate as activethreads |
1212 |
stdout.write('\nFinishing active threads ')
|
1213 |
for thread in activethreads(): |
1214 |
stdout.flush() |
1215 |
try:
|
1216 |
thread.join() |
1217 |
stdout.write('.')
|
1218 |
except RuntimeError: |
1219 |
continue
|
1220 |
try:
|
1221 |
progress_bar.finish() |
1222 |
except Exception: |
1223 |
pass
|
1224 |
print('\ndownload canceled by user')
|
1225 |
if local_path is not None: |
1226 |
print('to resume, re-run with --resume')
|
1227 |
except Exception as e: |
1228 |
try:
|
1229 |
progress_bar.finish() |
1230 |
except Exception: |
1231 |
pass
|
1232 |
raiseCLIError(e) |
1233 |
print
|
1234 |
|
1235 |
|
1236 |
@command(pithos_cmds)
|
1237 |
class store_hashmap(_store_container_command): |
1238 |
"""Get the hash-map of an object"""
|
1239 |
|
1240 |
arguments = dict(
|
1241 |
if_match=ValueArgument('show output if ETags match', '--if-match'), |
1242 |
if_none_match=ValueArgument( |
1243 |
'show output if ETags match',
|
1244 |
'--if-none-match'),
|
1245 |
if_modified_since=DateArgument( |
1246 |
'show output modified since then',
|
1247 |
'--if-modified-since'),
|
1248 |
if_unmodified_since=DateArgument( |
1249 |
'show output unmodified since then',
|
1250 |
'--if-unmodified-since'),
|
1251 |
object_version=ValueArgument( |
1252 |
'get the specific version',
|
1253 |
'--object-version')
|
1254 |
) |
1255 |
|
1256 |
def main(self, container___path): |
1257 |
super(self.__class__, self).main( |
1258 |
container___path, |
1259 |
path_is_optional=False)
|
1260 |
try:
|
1261 |
data = self.client.get_object_hashmap(
|
1262 |
self.path,
|
1263 |
version=self['object_version'], |
1264 |
if_match=self['if_match'], |
1265 |
if_none_match=self['if_none_match'], |
1266 |
if_modified_since=self['if_modified_since'], |
1267 |
if_unmodified_since=self['if_unmodified_since']) |
1268 |
except ClientError as err: |
1269 |
if err.status == 404: |
1270 |
if 'container' in ('%s' % err).lower(): |
1271 |
raiseCLIError( |
1272 |
err, |
1273 |
'No container %s in account %s'\
|
1274 |
% (self.container, self.account), |
1275 |
details=self.generic_err_details)
|
1276 |
elif 'object' in ('%s' % err).lower(): |
1277 |
raiseCLIError( |
1278 |
err, |
1279 |
'No object %s in container %s'\
|
1280 |
% (self.path, self.container), |
1281 |
details=self.generic_err_details)
|
1282 |
raise_connection_errors(err) |
1283 |
raiseCLIError(err) |
1284 |
except Exception as e: |
1285 |
raiseCLIError(e) |
1286 |
print_dict(data) |
1287 |
|
1288 |
|
1289 |
@command(pithos_cmds)
|
1290 |
class store_delete(_store_container_command): |
1291 |
"""Delete a container [or an object]
|
1292 |
How to delete a non-empty container:
|
1293 |
- empty the container: /store delete -r <container>
|
1294 |
- delete it: /store delete <container>
|
1295 |
.
|
1296 |
Semantics of directory deletion:
|
1297 |
.a preserve the contents: /store delete <container>:<directory>
|
1298 |
. objects of the form dir/filename can exist with a dir object
|
1299 |
.b delete contents: /store delete -r <container>:<directory>
|
1300 |
. all dir/* objects are affected, even if dir does not exist
|
1301 |
.
|
1302 |
To restore a deleted object OBJ in a container CONT:
|
1303 |
- get object versions: /store versions CONT:OBJ
|
1304 |
. and choose the version to be restored
|
1305 |
- restore the object: /store copy --source-version=<version> CONT:OBJ OBJ
|
1306 |
"""
|
1307 |
|
1308 |
arguments = dict(
|
1309 |
until=DateArgument('remove history until that date', '--until'), |
1310 |
yes=FlagArgument('Do not prompt for permission', '--yes'), |
1311 |
recursive=FlagArgument( |
1312 |
'empty dir or container and delete (if dir)',
|
1313 |
('-r', '--recursive')) |
1314 |
) |
1315 |
|
1316 |
def __init__(self, arguments={}): |
1317 |
super(self.__class__, self).__init__(arguments) |
1318 |
self['delimiter'] = DelimiterArgument( |
1319 |
self,
|
1320 |
parsed_name='--delimiter',
|
1321 |
help='delete objects prefixed with <object><delimiter>')
|
1322 |
|
1323 |
def main(self, container____path__): |
1324 |
super(self.__class__, self).main(container____path__) |
1325 |
try:
|
1326 |
if (not self.path): |
1327 |
if self['yes'] or ask_user( |
1328 |
'Delete container %s ?' % self.container): |
1329 |
self.client.del_container(
|
1330 |
until=self['until'], |
1331 |
delimiter=self['delimiter']) |
1332 |
else:
|
1333 |
if self['yes'] or ask_user( |
1334 |
'Delete %s:%s ?' % (self.container, self.path)): |
1335 |
self.client.del_object(
|
1336 |
self.path,
|
1337 |
until=self['until'], |
1338 |
delimiter=self['delimiter']) |
1339 |
except ClientError as err: |
1340 |
if err.status == 404: |
1341 |
if 'container' in ('%s' % err).lower(): |
1342 |
raiseCLIError( |
1343 |
err, |
1344 |
'No container %s in account %s'\
|
1345 |
% (self.container, self.account), |
1346 |
details=self.generic_err_details)
|
1347 |
elif 'object' in ('%s' % err).lower(): |
1348 |
raiseCLIError( |
1349 |
err, |
1350 |
'No object %s in container %s'\
|
1351 |
% (self.path, self.container), |
1352 |
details=self.generic_err_details)
|
1353 |
raise_connection_errors(err) |
1354 |
raiseCLIError(err) |
1355 |
except Exception as e: |
1356 |
raiseCLIError(e) |
1357 |
|
1358 |
|
1359 |
@command(pithos_cmds)
|
1360 |
class store_purge(_store_container_command): |
1361 |
"""Delete a container and release related data blocks
|
1362 |
Non-empty containers can not purged.
|
1363 |
To purge a container with content:
|
1364 |
. /store delete -r <container>
|
1365 |
. objects are deleted, but data blocks remain on server
|
1366 |
. /store purge <container>
|
1367 |
. container and data blocks are released and deleted
|
1368 |
"""
|
1369 |
|
1370 |
arguments = dict(
|
1371 |
yes=FlagArgument('Do not prompt for permission', '--yes'), |
1372 |
) |
1373 |
|
1374 |
def main(self, container): |
1375 |
super(self.__class__, self).main(container) |
1376 |
try:
|
1377 |
if self['yes'] or ask_user( |
1378 |
'Purge container %s?' % self.container): |
1379 |
self.client.purge_container()
|
1380 |
except ClientError as err: |
1381 |
if err.status == 404: |
1382 |
if 'container' in ('%s' % err).lower(): |
1383 |
raiseCLIError( |
1384 |
err, |
1385 |
'No container %s in account %s'\
|
1386 |
% (self.container, self.account), |
1387 |
details=self.generic_err_details)
|
1388 |
raise_connection_errors(err) |
1389 |
raiseCLIError(err) |
1390 |
except Exception as e: |
1391 |
raiseCLIError(e) |
1392 |
|
1393 |
|
1394 |
@command(pithos_cmds)
|
1395 |
class store_publish(_store_container_command): |
1396 |
"""Publish the object and print the public url"""
|
1397 |
|
1398 |
def main(self, container___path): |
1399 |
super(self.__class__, self).main( |
1400 |
container___path, |
1401 |
path_is_optional=False)
|
1402 |
try:
|
1403 |
url = self.client.publish_object(self.path) |
1404 |
except ClientError as err: |
1405 |
if err.status == 404: |
1406 |
if 'container' in ('%s' % err).lower(): |
1407 |
raiseCLIError( |
1408 |
err, |
1409 |
'No container %s in account %s'\
|
1410 |
% (self.container, self.account), |
1411 |
details=self.generic_err_details)
|
1412 |
elif 'object' in ('%s' % err).lower(): |
1413 |
raiseCLIError( |
1414 |
err, |
1415 |
'No object %s in container %s'\
|
1416 |
% (self.path, self.container), |
1417 |
details=self.generic_err_details)
|
1418 |
raise_connection_errors(err) |
1419 |
raiseCLIError(err) |
1420 |
except Exception as e: |
1421 |
raiseCLIError(e) |
1422 |
print(url) |
1423 |
|
1424 |
|
1425 |
@command(pithos_cmds)
|
1426 |
class store_unpublish(_store_container_command): |
1427 |
"""Unpublish an object"""
|
1428 |
|
1429 |
def main(self, container___path): |
1430 |
super(self.__class__, self).main( |
1431 |
container___path, |
1432 |
path_is_optional=False)
|
1433 |
try:
|
1434 |
self.client.unpublish_object(self.path) |
1435 |
except ClientError as err: |
1436 |
if err.status == 404: |
1437 |
if 'container' in ('%s' % err).lower(): |
1438 |
raiseCLIError( |
1439 |
err, |
1440 |
'No container %s in account %s'\
|
1441 |
% (self.container, self.account), |
1442 |
details=self.generic_err_details)
|
1443 |
elif 'object' in ('%s' % err).lower(): |
1444 |
raiseCLIError( |
1445 |
err, |
1446 |
'No object %s in container %s'\
|
1447 |
% (self.path, self.container), |
1448 |
details=self.generic_err_details)
|
1449 |
raise_connection_errors(err) |
1450 |
raiseCLIError(err) |
1451 |
except Exception as e: |
1452 |
raiseCLIError(e) |
1453 |
|
1454 |
|
1455 |
@command(pithos_cmds)
|
1456 |
class store_permissions(_store_container_command): |
1457 |
"""Get read and write permissions of an object
|
1458 |
Permissions are lists of users and user groups. There is read and write
|
1459 |
permissions. Users and groups with write permission have also read
|
1460 |
permission.
|
1461 |
"""
|
1462 |
|
1463 |
def main(self, container___path): |
1464 |
super(self.__class__, self).main( |
1465 |
container___path, |
1466 |
path_is_optional=False)
|
1467 |
try:
|
1468 |
reply = self.client.get_object_sharing(self.path) |
1469 |
except ClientError as err: |
1470 |
if err.status == 404: |
1471 |
if 'container' in ('%s' % err).lower(): |
1472 |
raiseCLIError( |
1473 |
err, |
1474 |
'No container %s in account %s'\
|
1475 |
% (self.container, self.account), |
1476 |
details=self.generic_err_details)
|
1477 |
elif 'object' in ('%s' % err).lower(): |
1478 |
raiseCLIError( |
1479 |
err, |
1480 |
'No object %s in container %s'\
|
1481 |
% (self.path, self.container), |
1482 |
details=self.generic_err_details)
|
1483 |
raise_connection_errors(err) |
1484 |
raiseCLIError(err) |
1485 |
except Exception as e: |
1486 |
raiseCLIError(e) |
1487 |
print_dict(reply) |
1488 |
|
1489 |
|
1490 |
@command(pithos_cmds)
|
1491 |
class store_setpermissions(_store_container_command): |
1492 |
"""Set permissions for an object
|
1493 |
New permissions overwrite existing permissions.
|
1494 |
Permission format:
|
1495 |
- read=<username>[,usergroup[,...]]
|
1496 |
- write=<username>[,usegroup[,...]]
|
1497 |
E.g. to give read permissions for file F to users A and B and write for C:
|
1498 |
. /store setpermissions F read=A,B write=C
|
1499 |
"""
|
1500 |
|
1501 |
def format_permition_dict(self, permissions): |
1502 |
read = False
|
1503 |
write = False
|
1504 |
for perms in permissions: |
1505 |
splstr = perms.split('=')
|
1506 |
if 'read' == splstr[0]: |
1507 |
read = [user_or_group.strip() \ |
1508 |
for user_or_group in splstr[1].split(',')] |
1509 |
elif 'write' == splstr[0]: |
1510 |
write = [user_or_group.strip() \ |
1511 |
for user_or_group in splstr[1].split(',')] |
1512 |
else:
|
1513 |
read = False
|
1514 |
write = False
|
1515 |
if not read and not write: |
1516 |
raiseCLIError(None,
|
1517 |
'Usage:\tread=<groups,users> write=<groups,users>')
|
1518 |
return (read, write)
|
1519 |
|
1520 |
def main(self, container___path, *permissions): |
1521 |
super(self.__class__, |
1522 |
self).main(container___path, path_is_optional=False) |
1523 |
(read, write) = self.format_permition_dict(permissions)
|
1524 |
try:
|
1525 |
self.client.set_object_sharing(self.path, |
1526 |
read_permition=read, write_permition=write) |
1527 |
except ClientError as err: |
1528 |
if err.status == 404: |
1529 |
if 'container' in ('%s' % err).lower(): |
1530 |
raiseCLIError( |
1531 |
err, |
1532 |
'No container %s in account %s'\
|
1533 |
% (self.container, self.account), |
1534 |
details=self.generic_err_details)
|
1535 |
elif 'object' in ('%s' % err).lower(): |
1536 |
raiseCLIError( |
1537 |
err, |
1538 |
'No object %s in container %s'\
|
1539 |
% (self.path, self.container), |
1540 |
details=self.generic_err_details)
|
1541 |
raise_connection_errors(err) |
1542 |
raiseCLIError(err) |
1543 |
except Exception as e: |
1544 |
raiseCLIError(e) |
1545 |
|
1546 |
|
1547 |
@command(pithos_cmds)
|
1548 |
class store_delpermissions(_store_container_command): |
1549 |
"""Delete all permissions set on object
|
1550 |
To modify permissions, use /store setpermssions
|
1551 |
"""
|
1552 |
|
1553 |
def main(self, container___path): |
1554 |
super(self.__class__, |
1555 |
self).main(container___path, path_is_optional=False) |
1556 |
try:
|
1557 |
self.client.del_object_sharing(self.path) |
1558 |
except ClientError as err: |
1559 |
if err.status == 404: |
1560 |
if 'container' in ('%s' % err).lower(): |
1561 |
raiseCLIError( |
1562 |
err, |
1563 |
'No container %s in account %s'\
|
1564 |
% (self.container, self.account), |
1565 |
details=self.generic_err_details)
|
1566 |
elif 'object' in ('%s' % err).lower(): |
1567 |
raiseCLIError( |
1568 |
err, |
1569 |
'No object %s in container %s'\
|
1570 |
% (self.path, self.container), |
1571 |
details=self.generic_err_details)
|
1572 |
raise_connection_errors(err) |
1573 |
raiseCLIError(err) |
1574 |
except Exception as e: |
1575 |
raiseCLIError(e) |
1576 |
|
1577 |
|
1578 |
@command(pithos_cmds)
|
1579 |
class store_info(_store_container_command): |
1580 |
"""Get detailed information for user account, containers or objects
|
1581 |
to get account info: /store info
|
1582 |
to get container info: /store info <container>
|
1583 |
to get object info: /store info <container>:<path>
|
1584 |
"""
|
1585 |
|
1586 |
arguments = dict(
|
1587 |
object_version=ValueArgument( |
1588 |
'show specific version \ (applies only for objects)',
|
1589 |
'--object-version')
|
1590 |
) |
1591 |
|
1592 |
def main(self, container____path__=None): |
1593 |
super(self.__class__, self).main(container____path__) |
1594 |
try:
|
1595 |
if self.container is None: |
1596 |
reply = self.client.get_account_info()
|
1597 |
elif self.path is None: |
1598 |
reply = self.client.get_container_info(self.container) |
1599 |
else:
|
1600 |
reply = self.client.get_object_info(
|
1601 |
self.path,
|
1602 |
version=self['object_version']) |
1603 |
except ClientError as err: |
1604 |
if err.status == 404: |
1605 |
if 'container' in ('%s' % err).lower(): |
1606 |
raiseCLIError( |
1607 |
err, |
1608 |
'No container %s in account %s'\
|
1609 |
% (self.container, self.account), |
1610 |
details=self.generic_err_details)
|
1611 |
elif 'object' in ('%s' % err).lower(): |
1612 |
raiseCLIError( |
1613 |
err, |
1614 |
'No object %s in container %s'\
|
1615 |
% (self.path, self.container), |
1616 |
details=self.generic_err_details)
|
1617 |
raise_connection_errors(err) |
1618 |
raiseCLIError(err) |
1619 |
except Exception as e: |
1620 |
raiseCLIError(e) |
1621 |
print_dict(reply) |
1622 |
|
1623 |
|
1624 |
@command(pithos_cmds)
|
1625 |
class store_meta(_store_container_command): |
1626 |
"""Get metadata for account, containers or objects"""
|
1627 |
|
1628 |
arguments = dict(
|
1629 |
detail=FlagArgument('show detailed output', '-l'), |
1630 |
until=DateArgument('show metadata until then', '--until'), |
1631 |
object_version=ValueArgument( |
1632 |
'show specific version \ (applies only for objects)',
|
1633 |
'--object-version')
|
1634 |
) |
1635 |
|
1636 |
def main(self, container____path__=None): |
1637 |
super(self.__class__, self).main(container____path__) |
1638 |
|
1639 |
detail = self['detail'] |
1640 |
try:
|
1641 |
until = self['until'] |
1642 |
if self.container is None: |
1643 |
if detail:
|
1644 |
reply = self.client.get_account_info(until=until)
|
1645 |
else:
|
1646 |
reply = self.client.get_account_meta(until=until)
|
1647 |
reply = pretty_keys(reply, '-')
|
1648 |
if reply:
|
1649 |
print(bold(self.client.account))
|
1650 |
elif self.path is None: |
1651 |
if detail:
|
1652 |
reply = self.client.get_container_info(until=until)
|
1653 |
else:
|
1654 |
cmeta = self.client.get_container_meta(until=until)
|
1655 |
ometa = self.client.get_container_object_meta(until=until)
|
1656 |
reply = {} |
1657 |
if cmeta:
|
1658 |
reply['container-meta'] = pretty_keys(cmeta, '-') |
1659 |
if ometa:
|
1660 |
reply['object-meta'] = pretty_keys(ometa, '-') |
1661 |
else:
|
1662 |
if detail:
|
1663 |
reply = self.client.get_object_info(self.path, |
1664 |
version=self['object_version']) |
1665 |
else:
|
1666 |
reply = self.client.get_object_meta(self.path, |
1667 |
version=self['object_version']) |
1668 |
if reply:
|
1669 |
reply = pretty_keys(pretty_keys(reply, '-'))
|
1670 |
except ClientError as err: |
1671 |
if err.status == 404: |
1672 |
if 'container' in ('%s' % err).lower(): |
1673 |
raiseCLIError( |
1674 |
err, |
1675 |
'No container %s in account %s'\
|
1676 |
% (self.container, self.account), |
1677 |
details=self.generic_err_details)
|
1678 |
elif 'object' in ('%s' % err).lower(): |
1679 |
raiseCLIError( |
1680 |
err, |
1681 |
'No object %s in container %s'\
|
1682 |
% (self.path, self.container), |
1683 |
details=self.generic_err_details)
|
1684 |
else:
|
1685 |
raiseCLIError(err, details=self.generic_err_details)
|
1686 |
raise_connection_errors(err) |
1687 |
raiseCLIError(err) |
1688 |
except Exception as e: |
1689 |
raiseCLIError(e) |
1690 |
if reply:
|
1691 |
print_dict(reply) |
1692 |
|
1693 |
|
1694 |
@command(pithos_cmds)
|
1695 |
class store_setmeta(_store_container_command): |
1696 |
"""Set a piece of metadata for account, container or object
|
1697 |
Metadata are formed as key:value pairs
|
1698 |
"""
|
1699 |
|
1700 |
def main(self, metakey___metaval, container____path__=None): |
1701 |
super(self.__class__, self).main(container____path__) |
1702 |
try:
|
1703 |
metakey, metavalue = metakey___metaval.split(':')
|
1704 |
except ValueError as err: |
1705 |
raiseCLIError(err, |
1706 |
'Cannot parse %s as a key:value pair' % metakey___metaval,
|
1707 |
details=['Syntax:',
|
1708 |
' store setmeta metakey:metavalue [cont[:path]]'
|
1709 |
], |
1710 |
importance=1,
|
1711 |
) |
1712 |
try:
|
1713 |
if self.container is None: |
1714 |
self.client.set_account_meta({metakey: metavalue})
|
1715 |
elif self.path is None: |
1716 |
self.client.set_container_meta({metakey: metavalue})
|
1717 |
else:
|
1718 |
self.client.set_object_meta(self.path, {metakey: metavalue}) |
1719 |
except ClientError as err: |
1720 |
if err.status == 404: |
1721 |
if 'container' in ('%s' % err).lower(): |
1722 |
raiseCLIError( |
1723 |
err, |
1724 |
'No container %s in account %s'\
|
1725 |
% (self.container, self.account), |
1726 |
details=self.generic_err_details)
|
1727 |
elif 'object' in ('%s' % err).lower(): |
1728 |
raiseCLIError( |
1729 |
err, |
1730 |
'No object %s in container %s'\
|
1731 |
% (self.path, self.container), |
1732 |
details=self.generic_err_details)
|
1733 |
else:
|
1734 |
raiseCLIError(err, details=self.generic_err_details)
|
1735 |
raise_connection_errors(err) |
1736 |
raiseCLIError(err) |
1737 |
except Exception as err: |
1738 |
raiseCLIError(err) |
1739 |
|
1740 |
|
1741 |
@command(pithos_cmds)
|
1742 |
class store_delmeta(_store_container_command): |
1743 |
"""Delete metadata with given key from account, container or object
|
1744 |
Metadata are formed as key:value objects
|
1745 |
- to get metadata of current account: /store meta
|
1746 |
- to get metadata of a container: /store meta <container>
|
1747 |
- to get metadata of an object: /store meta <container>:<path>
|
1748 |
"""
|
1749 |
|
1750 |
def main(self, metakey, container____path__=None): |
1751 |
super(self.__class__, self).main(container____path__) |
1752 |
try:
|
1753 |
if self.container is None: |
1754 |
self.client.del_account_meta(metakey)
|
1755 |
elif self.path is None: |
1756 |
self.client.del_container_meta(metakey)
|
1757 |
else:
|
1758 |
self.client.del_object_meta(self.path, metakey) |
1759 |
except ClientError as err: |
1760 |
if err.status == 404: |
1761 |
if 'container' in ('%s' % err).lower(): |
1762 |
raiseCLIError( |
1763 |
err, |
1764 |
'No container %s in account %s'\
|
1765 |
% (self.container, self.account), |
1766 |
details=self.generic_err_details)
|
1767 |
elif 'object' in ('%s' % err).lower(): |
1768 |
raiseCLIError( |
1769 |
err, |
1770 |
'No object %s in container %s'\
|
1771 |
% (self.path, self.container), |
1772 |
details=self.generic_err_details)
|
1773 |
else:
|
1774 |
raiseCLIError(err, details=self.generic_err_details)
|
1775 |
raise_connection_errors(err) |
1776 |
raiseCLIError(err) |
1777 |
except Exception as err: |
1778 |
raiseCLIError(err) |
1779 |
|
1780 |
|
1781 |
@command(pithos_cmds)
|
1782 |
class store_quota(_store_account_command): |
1783 |
"""Get quota (in KB) for account or container"""
|
1784 |
|
1785 |
def main(self, container=None): |
1786 |
super(self.__class__, self).main() |
1787 |
try:
|
1788 |
if container is None: |
1789 |
reply = self.client.get_account_quota()
|
1790 |
else:
|
1791 |
reply = self.client.get_container_quota(container)
|
1792 |
except ClientError as err: |
1793 |
if err.status == 404: |
1794 |
if 'container' in ('%s' % err).lower(): |
1795 |
raiseCLIError(err, |
1796 |
'No container %s in account %s'\
|
1797 |
% (container, self.account))
|
1798 |
raise_connection_errors(err) |
1799 |
raiseCLIError(err) |
1800 |
except Exception as err: |
1801 |
raiseCLIError(err) |
1802 |
print_dict(pretty_keys(reply, '-'))
|
1803 |
|
1804 |
|
1805 |
@command(pithos_cmds)
|
1806 |
class store_setquota(_store_account_command): |
1807 |
"""Set new quota (in KB) for account or container"""
|
1808 |
|
1809 |
def main(self, quota, container=None): |
1810 |
super(self.__class__, self).main() |
1811 |
try:
|
1812 |
if container is None: |
1813 |
self.client.set_account_quota(quota)
|
1814 |
else:
|
1815 |
self.client.container = container
|
1816 |
self.client.set_container_quota(quota)
|
1817 |
except ClientError as err: |
1818 |
if err.status == 404: |
1819 |
if 'container' in ('%s' % err).lower(): |
1820 |
raiseCLIError(err, |
1821 |
'No container %s in account %s'\
|
1822 |
% (container, self.account))
|
1823 |
raise_connection_errors(err) |
1824 |
raiseCLIError(err) |
1825 |
except Exception as err: |
1826 |
raiseCLIError(err) |
1827 |
|
1828 |
|
1829 |
@command(pithos_cmds)
|
1830 |
class store_versioning(_store_account_command): |
1831 |
"""Get versioning for account or container"""
|
1832 |
|
1833 |
def main(self, container=None): |
1834 |
super(self.__class__, self).main() |
1835 |
try:
|
1836 |
if container is None: |
1837 |
reply = self.client.get_account_versioning()
|
1838 |
else:
|
1839 |
reply = self.client.get_container_versioning(container)
|
1840 |
except ClientError as err: |
1841 |
if err.status == 404: |
1842 |
if 'container' in ('%s' % err).lower(): |
1843 |
raiseCLIError( |
1844 |
err, |
1845 |
'No container %s in account %s'\
|
1846 |
% (self.container, self.account), |
1847 |
details=self.generic_err_details)
|
1848 |
else:
|
1849 |
raiseCLIError(err, details=self.generic_err_details)
|
1850 |
raise_connection_errors(err) |
1851 |
raiseCLIError(err) |
1852 |
except Exception as err: |
1853 |
raiseCLIError(err) |
1854 |
print_dict(reply) |
1855 |
|
1856 |
|
1857 |
@command(pithos_cmds)
|
1858 |
class store_setversioning(_store_account_command): |
1859 |
"""Set versioning mode (auto, none) for account or container"""
|
1860 |
|
1861 |
def main(self, versioning, container=None): |
1862 |
super(self.__class__, self).main() |
1863 |
try:
|
1864 |
if container is None: |
1865 |
self.client.set_account_versioning(versioning)
|
1866 |
else:
|
1867 |
self.client.container = container
|
1868 |
self.client.set_container_versioning(versioning)
|
1869 |
except ClientError as err: |
1870 |
if err.status == 404: |
1871 |
if 'container' in ('%s' % err).lower(): |
1872 |
raiseCLIError( |
1873 |
err, |
1874 |
'No container %s in account %s'\
|
1875 |
% (self.container, self.account), |
1876 |
details=self.generic_err_details)
|
1877 |
else:
|
1878 |
raiseCLIError(err, details=self.generic_err_details)
|
1879 |
raise_connection_errors(err) |
1880 |
raiseCLIError(err) |
1881 |
except Exception as err: |
1882 |
raiseCLIError(err) |
1883 |
|
1884 |
|
1885 |
@command(pithos_cmds)
|
1886 |
class store_group(_store_account_command): |
1887 |
"""Get groups and group members"""
|
1888 |
|
1889 |
def main(self): |
1890 |
super(self.__class__, self).main() |
1891 |
try:
|
1892 |
reply = self.client.get_account_group()
|
1893 |
except ClientError as err: |
1894 |
raise_connection_errors(err) |
1895 |
raiseCLIError(err) |
1896 |
except Exception as err: |
1897 |
raiseCLIError(err) |
1898 |
print_dict(pretty_keys(reply, '-'))
|
1899 |
|
1900 |
|
1901 |
@command(pithos_cmds)
|
1902 |
class store_setgroup(_store_account_command): |
1903 |
"""Set a user group"""
|
1904 |
|
1905 |
def main(self, groupname, *users): |
1906 |
super(self.__class__, self).main() |
1907 |
try:
|
1908 |
self.client.set_account_group(groupname, users)
|
1909 |
except ClientError as err: |
1910 |
raise_connection_errors(err) |
1911 |
raiseCLIError(err) |
1912 |
except Exception as err: |
1913 |
raiseCLIError(err) |
1914 |
|
1915 |
|
1916 |
@command(pithos_cmds)
|
1917 |
class store_delgroup(_store_account_command): |
1918 |
"""Delete a user group"""
|
1919 |
|
1920 |
def main(self, groupname): |
1921 |
super(self.__class__, self).main() |
1922 |
try:
|
1923 |
self.client.del_account_group(groupname)
|
1924 |
except ClientError as err: |
1925 |
raise_connection_errors(err) |
1926 |
raiseCLIError(err) |
1927 |
except Exception as err: |
1928 |
raiseCLIError(err) |
1929 |
|
1930 |
|
1931 |
@command(pithos_cmds)
|
1932 |
class store_sharers(_store_account_command): |
1933 |
"""List the accounts that share objects with current user"""
|
1934 |
|
1935 |
arguments = dict(
|
1936 |
detail=FlagArgument('show detailed output', '-l'), |
1937 |
marker=ValueArgument('show output greater then marker', '--marker') |
1938 |
) |
1939 |
|
1940 |
def main(self): |
1941 |
super(self.__class__, self).main() |
1942 |
try:
|
1943 |
marker = self['marker'] |
1944 |
accounts = self.client.get_sharing_accounts(marker=marker)
|
1945 |
except ClientError as err: |
1946 |
raise_connection_errors(err) |
1947 |
raiseCLIError(err) |
1948 |
except Exception as err: |
1949 |
raiseCLIError(err) |
1950 |
|
1951 |
for acc in accounts: |
1952 |
stdout.write(bold(acc['name']) + ' ') |
1953 |
if self['detail']: |
1954 |
print_dict(acc, exclude='name')
|
1955 |
if not self['detail']: |
1956 |
print
|
1957 |
|
1958 |
|
1959 |
@command(pithos_cmds)
|
1960 |
class store_versions(_store_container_command): |
1961 |
"""Get the list of object versions
|
1962 |
Deleted objects may still have versions that can be used to restore it and
|
1963 |
get information about its previous state.
|
1964 |
The version number can be used in a number of other commands, like info,
|
1965 |
copy, move, meta. See these commands for more information, e.g.
|
1966 |
/store info -h
|
1967 |
"""
|
1968 |
|
1969 |
def main(self, container___path): |
1970 |
super(store_versions, self).main(container___path) |
1971 |
try:
|
1972 |
versions = self.client.get_object_versionlist(self.path) |
1973 |
|
1974 |
for vitem in versions: |
1975 |
t = localtime(float(vitem[1])) |
1976 |
vid = bold(unicode(vitem[0])) |
1977 |
print('\t%s \t(%s)' % (vid, strftime('%d-%m-%Y %H:%M:%S', t))) |
1978 |
except ClientError as err: |
1979 |
if err.status == 404: |
1980 |
if 'container' in ('%s' % err).lower(): |
1981 |
raiseCLIError( |
1982 |
err, |
1983 |
'No container %s in account %s'\
|
1984 |
% (self.container, self.account), |
1985 |
details=self.generic_err_details)
|
1986 |
elif 'object' in ('%s' % err).lower(): |
1987 |
raiseCLIError( |
1988 |
err, |
1989 |
'No object %s in container %s'\
|
1990 |
% (self.path, self.container), |
1991 |
details=self.generic_err_details)
|
1992 |
else:
|
1993 |
raiseCLIError(err, details=self.generic_err_details)
|
1994 |
raise_connection_errors(err) |
1995 |
raiseCLIError(err) |
1996 |
except Exception as err: |
1997 |
raiseCLIError(err) |