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)
|