Merge branch 'feature-json-output' into develop
authorStavros Sachtouris <saxtouri@admin.grnet.gr>
Wed, 22 May 2013 15:05:00 +0000 (18:05 +0300)
committerStavros Sachtouris <saxtouri@admin.grnet.gr>
Wed, 22 May 2013 15:05:00 +0000 (18:05 +0300)
Conflicts:
Changelog
kamaki/cli/commands/image.py

Also, take care of pep8 issues

1  2 
Changelog
kamaki/cli/commands/cyclades.py
kamaki/cli/commands/image.py
kamaki/cli/commands/pithos.py
kamaki/clients/pithos/__init__.py

diff --cc Changelog
+++ b/Changelog
@@@ -7,7 -7,7 +7,8 @@@ Bug Fixes
  - Restore 2nd level command syntax in shell [#3736]
  - Allow copy of deleted objects by refering to older version [#3737]
  - Add image.add_member missing content-length header
 +- Do not unpublish by default in all pithos low level requests [#3780]
+ - Unquote http respons headers
  
  Changes:
  
@@@ -55,7 -67,5 +68,8 @@@ Features
  - Add a download_to_string method in pithos client [#3608]
  - Add an upload_from_string method in pithos client [#3608]
  - Add pithos client method create_container [#3756]
 +- Store image properties on remote location after image registration [#3769]
 +- Add runtime args to image register for forcing or unsettitng property
 +    storage [#3769]
+ - Add server-firewall-get command to get a VMs firewall profile
  
@@@ -33,7 -33,7 +33,7 @@@
  
  from kamaki.cli import command
  from kamaki.cli.command_tree import CommandTree
- from kamaki.cli.utils import print_dict, print_list, print_items
 -from kamaki.cli.utils import print_dict, print_items, print_json
++from kamaki.cli.utils import print_dict
  from kamaki.cli.errors import raiseCLIError, CLISyntaxError
  from kamaki.clients.cyclades import CycladesClient, ClientError
  from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
  # interpreted as representing official policies, either expressed
  # or implied, of GRNET S.A.command
  
 +from json import load, dumps
 +from os.path import abspath
 +from logging import getLogger
 +
  from kamaki.cli import command
  from kamaki.cli.command_tree import CommandTree
--from kamaki.cli.utils import print_dict, print_items, print_json
++from kamaki.cli.utils import print_dict, print_json
  from kamaki.clients.image import ImageClient
 +from kamaki.clients.pithos import PithosClient
 +from kamaki.clients.astakos import AstakosClient
 +from kamaki.clients import ClientError
  from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
  from kamaki.cli.argument import IntArgument
  from kamaki.cli.commands.cyclades import _init_cyclades
- from kamaki.cli.commands import _command_init, errors, _optional_output_cmd
 +from kamaki.cli.errors import raiseCLIError
+ from kamaki.cli.commands import _command_init, errors
+ from kamaki.cli.commands import _optional_output_cmd, _optional_json
  
  
  image_cmds = CommandTree(
@@@ -83,50 -73,8 +84,50 @@@ class _init_image(_command_init)
  # Plankton Image Commands
  
  
 +def _validate_image_props(json_dict, return_str=False):
 +    """
 +    :param json_dict" (dict) json-formated, of the form
 +        {"key1": "val1", "key2": "val2", ...}
 +
 +    :param return_str: (boolean) if true, return a json dump
 +
 +    :returns: (dict)
 +
 +    :raises TypeError, AttributeError: Invalid json format
 +
 +    :raises AssertionError: Valid json but invalid image properties dict
 +    """
 +    json_str = dumps(json_dict, indent=2)
 +    for k, v in json_dict.items():
 +        dealbreaker = isinstance(v, dict) or isinstance(v, list)
 +        assert not dealbreaker, 'Invalid property value for key %s' % k
 +        dealbreaker = ' ' in k
 +        assert not dealbreaker, 'Invalid key [%s]' % k
 +        json_dict[k] = '%s' % v
 +    return json_str if return_str else json_dict
 +
 +
 +def _load_image_props(filepath):
 +    """
 +    :param filepath: (str) the (relative) path of the metafile
 +
 +    :returns: (dict) json_formated
 +
 +    :raises TypeError, AttributeError: Invalid json format
 +
 +    :raises AssertionError: Valid json but invalid image properties dict
 +    """
 +    with open(abspath(filepath)) as f:
 +        meta_dict = load(f)
 +        try:
 +            return _validate_image_props(meta_dict)
 +        except AssertionError:
 +            log.debug('Failed to load properties from file %s' % filepath)
 +            raise
 +
 +
  @command(image_cmds)
- class image_list(_init_image):
+ class image_list(_init_image, _optional_json):
      """List images accessible by user"""
  
      arguments = dict(
@@@ -260,85 -197,24 +250,82 @@@ class image_register(_init_image, _opti
              'add property in key=value form (can be repeated)',
              ('-p', '--property')),
          is_public=FlagArgument('mark image as public', '--public'),
 -        size=IntArgument('set image size', '--size')
 +        size=IntArgument('set image size', '--size'),
-         #update=FlagArgument(
-         #    'update existing image properties',
-         #    ('-u', '--update')),
-         json_output=FlagArgument('Show results in json', ('-j', '--json')),
 +        property_file=ValueArgument(
 +            'Load properties from a json-formated file <img-file>.meta :'
 +            '{"key1": "val1", "key2": "val2", ...}',
 +            ('--property-file')),
 +        prop_file_force=FlagArgument(
 +            'Store remote property object, even it already exists',
 +            ('-f', '--force-upload-property-file')),
 +        no_prop_file_upload=FlagArgument(
 +            'Do not store properties in remote property file',
 +            ('--no-property-file-upload')),
 +        container=ValueArgument(
 +            'Remote image container', ('-C', '--container')),
 +        fileowner=ValueArgument(
 +            'UUID of the user who owns the image file', ('--fileowner'))
++
      )
  
 +    def _get_uuid(self):
 +        uuid = self['fileowner'] or self.config.get('image', 'fileowner')
 +        if uuid:
 +            return uuid
 +        atoken = self.client.token
 +        user = AstakosClient(self.config.get('user', 'url'), atoken)
 +        return user.term('uuid')
 +
 +    def _get_pithos_client(self, uuid, container):
 +        purl = self.config.get('file', 'url')
 +        ptoken = self.client.token
 +        return PithosClient(purl, ptoken, uuid, container)
 +
 +    def _store_remote_property_file(self, pclient, remote_path, properties):
 +        return pclient.upload_from_string(
 +            remote_path, _validate_image_props(properties, return_str=True))
 +
 +    def _get_container_path(self, container_path):
 +        container = self['container'] or self.config.get('image', 'container')
 +        if container:
 +            return container, container_path
 +
 +        container, sep, path = container_path.partition(':')
 +        if not sep or not container or not path:
 +            raiseCLIError(
 +                '%s is not a valid pithos+ remote location' % container_path,
 +                importance=2,
 +                details=[
 +                    'To set "image" as container and "my_dir/img.diskdump" as',
 +                    'the image path, try one of the following as '
 +                    'container:path',
 +                    '- <image container>:<remote path>',
 +                    '    e.g. image:/my_dir/img.diskdump',
 +                    '- <remote path> -C <image container>',
 +                    '    e.g. /my_dir/img.diskdump -C image'])
 +        return container, path
 +
      @errors.generic.all
 +    @errors.plankton.image_file
      @errors.plankton.connection
 -    def _run(self, name, location):
 -        if not location.startswith('pithos://'):
 -            account = self.config.get('file', 'account') \
 -                or self.config.get('global', 'account')
 -            assert account, 'No user account provided'
 -            if account[-1] == '/':
 -                account = account[:-1]
 -            container = self.config.get('file', 'container') \
 -                or self.config.get('global', 'container')
 -            if not container:
 -                location = 'pithos://%s/%s' % (account, location)
 -            else:
 -                location = 'pithos://%s/%s/%s' % (account, container, location)
 +    def _run(self, name, container_path):
 +        container, path = self._get_container_path(container_path)
 +        uuid = self._get_uuid()
 +        prop_path = '%s.meta' % path
 +
 +        pclient = None if (
 +            self['no_prop_file_upload']) else self._get_pithos_client(
 +                uuid, container)
 +        if pclient and not self['prop_file_force']:
 +            try:
 +                pclient.get_object_info(prop_path)
 +                raiseCLIError('Property file %s: %s already exists' % (
 +                    container, prop_path))
 +            except ClientError as ce:
 +                if ce.status != 404:
 +                    raise
 +
 +        location = 'pithos://%s/%s/%s' % (uuid, container, path)
  
          params = {}
          for key in set([
                  'size',
                  'is_public']).intersection(self.arguments):
              params[key] = self[key]
+         properties = self['properties']
  
 +        #load properties
 +        properties = dict()
 +        pfile = self['property_file']
 +        if pfile:
 +            try:
 +                for k, v in _load_image_props(pfile).items():
 +                    properties[k.lower()] = v
 +            except Exception as e:
 +                raiseCLIError(
 +                    e, 'Format error in property file %s' % pfile,
 +                    details=[
 +                        'Expected content format:',
 +                        '  {',
 +                        '    "key1": "value1",',
 +                        '    "key2": "value2",',
 +                        '    ...',
 +                        '  }',
 +                        '',
 +                        'Parser:'
 +                    ])
 +        for k, v in self['properties'].items():
 +            properties[k.lower()] = v
 +
-         printer = print_json if self['json_output'] else print_dict
-         printer(self.client.register(name, location, params, properties))
+         self._print([self.client.register(name, location, params, properties)])
  
 -    def main(self, name, location):
 +        if pclient:
 +            prop_headers = pclient.upload_from_string(
 +                prop_path, _validate_image_props(properties, return_str=True))
 +            if self['json_output']:
 +                print_json(dict(
 +                    property_file_location='%s:%s' % (container, prop_path),
 +                    headers=prop_headers))
 +            else:
 +                print('Property file uploaded as %s:%s (version %s)' % (
 +                    container, prop_path, prop_headers['x-object-version']))
 +
 +    def main(self, name, container___path):
          super(self.__class__, self)._run()
 -        self._run(name, location)
 +        self._run(name, container___path)
  
  
  @command(image_cmds)
Simple merge
Simple merge