Revision ec6c3949 kamaki/cli/commands/image.py
b/kamaki/cli/commands/image.py | ||
---|---|---|
48 | 48 |
FlagArgument, ValueArgument, RepeatableArgument, KeyValueArgument, |
49 | 49 |
IntArgument, ProgressBarArgument) |
50 | 50 |
from kamaki.cli.commands.cyclades import _init_cyclades |
51 |
from kamaki.cli.errors import raiseCLIError, CLIBaseUrlError |
|
51 |
from kamaki.cli.errors import ( |
|
52 |
raiseCLIError, CLIBaseUrlError, CLIInvalidArgument) |
|
52 | 53 |
from kamaki.cli.commands import _command_init, errors, addLogSettings |
53 | 54 |
from kamaki.cli.commands import ( |
54 | 55 |
_optional_output_cmd, _optional_json, _name_filter, _id_filter) |
... | ... | |
373 | 374 |
self._run(image_id=image_id) |
374 | 375 |
|
375 | 376 |
|
377 |
class PithosLocationArgument(ValueArgument): |
|
378 |
"""Resolve pithos url, return in the form pithos://uuid/container/path""" |
|
379 |
|
|
380 |
def __init__( |
|
381 |
self, help=None, parsed_name=None, default=None, user_uuid=None): |
|
382 |
super(PithosLocationArgument, self).__init__( |
|
383 |
help=help, parsed_name=parsed_name, default=default) |
|
384 |
self.uuid, self.container, self.path = user_uuid, None, None |
|
385 |
|
|
386 |
def setdefault(self, term, value): |
|
387 |
if not getattr(self, term, None): |
|
388 |
setattr(self, term, value) |
|
389 |
|
|
390 |
@property |
|
391 |
def value(self): |
|
392 |
return 'pithos://%s/%s/%s' % (self.uuid, self.container, self.path) |
|
393 |
|
|
394 |
@value.setter |
|
395 |
def value(self, location): |
|
396 |
if location: |
|
397 |
from kamaki.cli.commands.pithos import _pithos_container as pc |
|
398 |
try: |
|
399 |
uuid, self.container, self.path = pc._resolve_pithos_url( |
|
400 |
location) |
|
401 |
self.uuid = uuid or self.uuid |
|
402 |
for term in ('container', 'path'): |
|
403 |
assert getattr(self, term, None), 'No %s' % term |
|
404 |
except Exception as e: |
|
405 |
raise CLIInvalidArgument( |
|
406 |
'Invalid Pithos+ location %s (%s)' % (location, e), |
|
407 |
details=[ |
|
408 |
'The image location must be a valid Pithos+', |
|
409 |
'location. There are two valid formats:', |
|
410 |
' pithos://USER_UUID/CONTAINER/PATH', |
|
411 |
'OR', |
|
412 |
' /CONTAINER/PATH', |
|
413 |
'To see all containers:', |
|
414 |
' [kamaki] container list', |
|
415 |
'To list the contents of a container:', |
|
416 |
' [kamaki] container list CONTAINER']) |
|
417 |
|
|
418 |
|
|
376 | 419 |
@command(image_cmds) |
377 | 420 |
class image_register(_init_image, _optional_json): |
378 | 421 |
"""(Re)Register an image file to an Image service |
... | ... | |
381 | 424 |
only automatically (e.g., image id). There are also some custom user |
382 | 425 |
metadata, called properties. |
383 | 426 |
A register command creates a remote meta file at |
384 |
. <container>:<image path>.meta
|
|
427 |
/<container>/<image path>.meta
|
|
385 | 428 |
Users may download and edit this file and use it to re-register one or more |
386 | 429 |
images. |
387 | 430 |
In case of a meta file, runtime arguments for metadata or properties |
... | ... | |
417 | 460 |
uuid=ValueArgument('Custom user uuid', '--uuid'), |
418 | 461 |
local_image_path=ValueArgument( |
419 | 462 |
'Local image file path to upload and register ' |
420 |
'(still need target file in the form container:remote-path )',
|
|
463 |
'(still need target file in the form /ontainer/remote-path )',
|
|
421 | 464 |
'--upload-image-file'), |
422 | 465 |
progress_bar=ProgressBarArgument( |
423 |
'Do not use progress bar', '--no-progress-bar', default=False) |
|
466 |
'Do not use progress bar', '--no-progress-bar', default=False), |
|
467 |
name=ValueArgument('The name of the new image', '--name'), |
|
468 |
pithos_location=PithosLocationArgument( |
|
469 |
'The Pithos+ image location to put the image at. Format: ' |
|
470 |
'pithos://USER_UUID/CONTAINER/IMAGE or ' |
|
471 |
'/CONTAINER/IMAGE', |
|
472 |
'--location') |
|
424 | 473 |
) |
474 |
required = ('name', 'pithos_location') |
|
425 | 475 |
|
426 |
def _get_user_id(self): |
|
427 |
atoken = self.client.token |
|
428 |
if getattr(self, 'auth_base', False): |
|
429 |
return self.auth_base.term('id', atoken) |
|
430 |
else: |
|
431 |
astakos_url = self.config.get('user', 'url') or self.config.get( |
|
432 |
'astakos', 'url') |
|
433 |
if not astakos_url: |
|
434 |
raise CLIBaseUrlError(service='astakos') |
|
435 |
user = AstakosClient(astakos_url, atoken) |
|
436 |
return user.term('id') |
|
437 |
|
|
438 |
def _get_pithos_client(self, container): |
|
476 |
def _get_pithos_client(self, locator): |
|
439 | 477 |
if self['no_metafile_upload']: |
440 | 478 |
return None |
441 | 479 |
ptoken = self.client.token |
... | ... | |
447 | 485 |
purl = self.config.get_cloud('pithos', 'url') |
448 | 486 |
if not purl: |
449 | 487 |
raise CLIBaseUrlError(service='pithos') |
450 |
return PithosClient(purl, ptoken, self._get_user_id(), container) |
|
451 |
|
|
452 |
def _store_remote_metafile(self, pclient, remote_path, metadata): |
|
453 |
return pclient.upload_from_string( |
|
454 |
remote_path, _validate_image_meta(metadata, return_str=True), |
|
455 |
container_info_cache=self.container_info_cache) |
|
488 |
return PithosClient(purl, ptoken, locator.uuid, locator.container) |
|
456 | 489 |
|
457 | 490 |
def _load_params_from_file(self, location): |
458 | 491 |
params, properties = dict(), dict() |
... | ... | |
488 | 521 |
for k, v in self['properties'].items(): |
489 | 522 |
properties[k.upper().replace('-', '_')] = v |
490 | 523 |
|
491 |
def _validate_location(self, location): |
|
492 |
if not location: |
|
493 |
raiseCLIError( |
|
494 |
'No image file location provided', |
|
495 |
importance=2, details=[ |
|
496 |
'An image location is needed. Image location format:', |
|
497 |
' <container>:<path>', |
|
498 |
' where an image file at the above location must exist.' |
|
499 |
] + howto_image_file) |
|
500 |
try: |
|
501 |
return _validate_image_location(location) |
|
502 |
except AssertionError as ae: |
|
503 |
raiseCLIError( |
|
504 |
ae, 'Invalid image location format', |
|
505 |
importance=1, details=[ |
|
506 |
'Valid image location format:', |
|
507 |
' <container>:<img-file-path>' |
|
508 |
] + howto_image_file) |
|
509 |
|
|
510 |
@staticmethod |
|
511 |
def _old_location_format(location): |
|
512 |
prefix = 'pithos://' |
|
513 |
try: |
|
514 |
if location.startswith(prefix): |
|
515 |
uuid, sep, rest = location[len(prefix):].partition('/') |
|
516 |
container, sep, path = rest.partition('/') |
|
517 |
return (uuid, container, path) |
|
518 |
except Exception as e: |
|
519 |
raiseCLIError(e, 'Invalid location format', details=[ |
|
520 |
'Correct location format:', ' <container>:<image path>']) |
|
521 |
return () |
|
522 |
|
|
523 |
def _mine_location(self, container_path): |
|
524 |
old_response = self._old_location_format(container_path) |
|
525 |
if old_response: |
|
526 |
return old_response |
|
527 |
uuid = self['uuid'] or (self._username2uuid(self['owner_name']) if ( |
|
528 |
self['owner_name']) else self._get_user_id()) |
|
529 |
if not uuid: |
|
530 |
if self['owner_name']: |
|
531 |
raiseCLIError('No user with username %s' % self['owner_name']) |
|
532 |
raiseCLIError('Failed to get user uuid', details=[ |
|
533 |
'For details on current user:', |
|
534 |
' /user whoami', |
|
535 |
'To authenticate a new user through a user token:', |
|
536 |
' /user authenticate <token>']) |
|
537 |
if self['container']: |
|
538 |
return uuid, self['container'], container_path |
|
539 |
container, sep, path = container_path.partition(':') |
|
540 |
if not (bool(container) and bool(path)): |
|
541 |
raiseCLIError( |
|
542 |
'Incorrect container-path format', importance=1, details=[ |
|
543 |
'Use : to seperate container form path', |
|
544 |
' <container>:<image-path>', |
|
545 |
'OR', |
|
546 |
'Use -C to specifiy a container', |
|
547 |
' -C <container> <image-path>'] + howto_image_file) |
|
548 |
|
|
549 |
return uuid, container, path |
|
550 |
|
|
551 | 524 |
@errors.generic.all |
552 | 525 |
@errors.plankton.connection |
553 |
@errors.pithos.container
|
|
554 |
def _run(self, name, uuid, dst_cont, img_path):
|
|
526 |
def _run(self, name, location):
|
|
527 |
locator = self.arguments['pithos_location']
|
|
555 | 528 |
if self['local_image_path']: |
556 | 529 |
with open(self['local_image_path']) as f: |
557 |
pithos = self._get_pithos_client(dst_cont)
|
|
530 |
pithos = self._get_pithos_client(locator)
|
|
558 | 531 |
(pbar, upload_cb) = self._safe_progress_bar('Uploading') |
559 | 532 |
if pbar: |
560 | 533 |
hash_bar = pbar.clone() |
561 | 534 |
hash_cb = hash_bar.get_generator('Calculating hashes') |
562 | 535 |
pithos.upload_object( |
563 |
img_path, f,
|
|
536 |
locator.path, f,
|
|
564 | 537 |
hash_cb=hash_cb, upload_cb=upload_cb, |
565 | 538 |
container_info_cache=self.container_info_cache) |
566 | 539 |
pbar.finish() |
567 | 540 |
|
568 |
location = 'pithos://%s/%s/%s' % (uuid, dst_cont, img_path) |
|
569 | 541 |
(params, properties, new_loc) = self._load_params_from_file(location) |
570 | 542 |
if location != new_loc: |
571 |
uuid, dst_cont, img_path = self._validate_location(new_loc)
|
|
543 |
locator.value = new_loc
|
|
572 | 544 |
self._load_params_from_args(params, properties) |
573 |
pclient = self._get_pithos_client(dst_cont)
|
|
545 |
pclient = self._get_pithos_client(locator)
|
|
574 | 546 |
|
575 | 547 |
#check if metafile exists |
576 |
meta_path = '%s.meta' % img_path
|
|
548 |
meta_path = '%s.meta' % locator.path
|
|
577 | 549 |
if pclient and not self['metafile_force']: |
578 | 550 |
try: |
579 | 551 |
pclient.get_object_info(meta_path) |
580 | 552 |
raiseCLIError( |
581 |
'Metadata file %s:%s already exists, abort' % (
|
|
582 |
dst_cont, meta_path),
|
|
553 |
'Metadata file /%s/%s already exists, abort' % (
|
|
554 |
locator.container, meta_path),
|
|
583 | 555 |
details=['Registration ABORTED', 'Try -f to overwrite']) |
584 | 556 |
except ClientError as ce: |
585 | 557 |
if ce.status != 404: |
... | ... | |
594 | 566 |
ce, 'Nonexistent image file location\n\t%s' % location, |
595 | 567 |
details=[ |
596 | 568 |
'Does the image file %s exist at container %s ?' % ( |
597 |
img_path, dst_cont)] + howto_image_file) |
|
569 |
locator.path, |
|
570 |
locator.container)] + howto_image_file) |
|
598 | 571 |
raise |
599 | 572 |
r['owner'] += ' (%s)' % self._uuid2username(r['owner']) |
600 | 573 |
self._print(r, self.print_dict) |
... | ... | |
607 | 580 |
container_info_cache=self.container_info_cache) |
608 | 581 |
except TypeError: |
609 | 582 |
self.error( |
610 |
'Failed to dump metafile %s:%s' % (dst_cont, meta_path)) |
|
583 |
'Failed to dump metafile /%s/%s' % ( |
|
584 |
locator.container, meta_path)) |
|
611 | 585 |
return |
612 | 586 |
if self['json_output'] or self['output_format']: |
613 | 587 |
self.print_json(dict( |
614 |
metafile_location='%s:%s' % (dst_cont, meta_path), |
|
588 |
metafile_location='/%s/%s' % ( |
|
589 |
locator.container, meta_path), |
|
615 | 590 |
headers=meta_headers)) |
616 | 591 |
else: |
617 |
self.error('Metadata file uploaded as %s:%s (version %s)' % ( |
|
618 |
dst_cont, meta_path, meta_headers['x-object-version'])) |
|
592 |
self.error('Metadata file uploaded as /%s/%s (version %s)' % ( |
|
593 |
locator.container, |
|
594 |
meta_path, |
|
595 |
meta_headers['x-object-version'])) |
|
619 | 596 |
|
620 |
def main(self, name, container___image_path):
|
|
597 |
def main(self): |
|
621 | 598 |
super(self.__class__, self)._run() |
622 |
self._run(name, *self._mine_location(container___image_path)) |
|
599 |
self.arguments['pithos_location'].setdefault( |
|
600 |
'uuid', self.auth_base.user_term('id')) |
|
601 |
self._run(self['name'], self['pithos_location']) |
|
623 | 602 |
|
624 | 603 |
|
625 | 604 |
@command(image_cmds) |
Also available in: Unified diff