Implement an optional json output 4 outputing cmds
authorStavros Sachtouris <saxtouri@admin.grnet.gr>
Wed, 22 May 2013 14:26:59 +0000 (17:26 +0300)
committerStavros Sachtouris <saxtouri@admin.grnet.gr>
Wed, 22 May 2013 14:26:59 +0000 (17:26 +0300)
Refs: #3732

Changelog
docs/commands.rst
docs/man/kamaki.rst
kamaki/cli/commands/__init__.py
kamaki/cli/commands/astakos.py
kamaki/cli/commands/cyclades.py
kamaki/cli/commands/image.py
kamaki/cli/commands/pithos.py
kamaki/cli/utils.py
kamaki/clients/__init__.py
kamaki/clients/pithos/__init__.py

index a88feb4..e0a13aa 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -7,6 +7,7 @@ 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
+- Unquote http respons headers
 
 Changes:
 
@@ -34,13 +35,22 @@ Changes:
     -image compute:
     delete, properties delete
     - server: rename, delete, reboot, start, shutdown, firewall-set
-    
+    - network: rename, delete, connect
+- Add optional json for methods with output [#3732]
+    - file:
+    list, hashmap, permissions-get, info, metadata-get, quota,
+    containerlimit-get, group-list, sharers, versions
+    - server: list, info, create, console, addr, metadata-list/set, stats
+    - image: list, meta, register, shared, list
+    - image compute: list, info, properties-list/get/add/set
+    - flavor: list, info
+    - network: info, list, create
+    - astakos: authenticate
 - Transliterate methods to list-get-set-delete command groups:
     - file: permissions, versioning, group and metadata
     - image: members, member
     - image compute: properties
     - server: firewall, metadata
-
 Features:
 
 - A logger module container a set of basic loging method for kamaki [#3668]
index 75128fc..b0dae5a 100644 (file)
@@ -326,7 +326,7 @@ file (Storage/Pithos+)
     download      :  Download a file or directory
     group         :  Manage access groups and group members
         delete:  Delete a user group
-        get   :  Get groups and group members
+        list  :  List groups and group members
         set   :  Set a user group
     hashmap       :  Get the hashmap of an object
     info          :  Get information for account [, container [or object]]
index 85de2da..0eb9d36 100644 (file)
@@ -191,7 +191,7 @@ file commands
 * download       Download a file or directory
 * group          Manage access groups and group members
     * delete     Delete a user group
-    * get        Get groups and group members
+    * list       List groups and group members
     * set        Set a user group
 * hashmap        Get the hashmap of an object
 * info           Get information for account [, container [or object]]
index e2255bd..da596bf 100644 (file)
@@ -45,6 +45,8 @@ class _command_init(object):
             arguments.update(self.arguments)
         if isinstance(self, _optional_output_cmd):
             arguments.update(self.oo_arguments)
+        if isinstance(self, _optional_json):
+            arguments.update(self.oj_arguments)
         self.arguments = dict(arguments)
         try:
             self.config = self['config']
@@ -129,6 +131,9 @@ class _command_init(object):
         return self[argterm]
 
 
+#  feature classes - inherit them to get special features for your commands
+
+
 class _optional_output_cmd(object):
 
     oo_arguments = dict(
@@ -141,3 +146,16 @@ class _optional_output_cmd(object):
             print_json(r)
         elif self['with_output']:
             print_items([r] if isinstance(r, dict) else r)
+
+
+class _optional_json(object):
+
+    oj_arguments = dict(
+        json_output=FlagArgument('show headers in json', ('-j', '--json'))
+    )
+
+    def _print(self, output, print_method=print_items, **print_method_kwargs):
+        if self['json_output']:
+            print_json(output)
+        else:
+            print_method(output, **print_method_kwargs)
index e86075f..81c9ff7 100644 (file)
 
 from kamaki.cli import command
 from kamaki.clients.astakos import AstakosClient
-from kamaki.cli.utils import print_dict, print_json
-from kamaki.cli.commands import _command_init, errors
+from kamaki.cli.commands import _command_init, errors, _optional_json
 from kamaki.cli.command_tree import CommandTree
-from kamaki.cli.argument import FlagArgument
 
 user_cmds = CommandTree('user', 'Astakos API commands')
 _commands = [user_cmds]
@@ -60,7 +58,7 @@ class _user_init(_command_init):
 
 
 @command(user_cmds)
-class user_authenticate(_user_init):
+class user_authenticate(_user_init, _optional_json):
     """Authenticate a user
     Get user information (e.g. unique account name) from token
     Token should be set in settings:
@@ -69,16 +67,13 @@ class user_authenticate(_user_init):
     Token can also be provided as a parameter
     """
 
-    arguments = dict(
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.user.authenticate
     def _run(self, custom_token=None):
         super(self.__class__, self)._run()
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.authenticate(custom_token))
+        self._print(
+            [self.client.authenticate(custom_token)],
+            title=('uuid', 'name',), with_redundancy=True)
 
     def main(self, custom_token=None):
         self._run(custom_token)
index 9b4b02e..939e691 100644 (file)
@@ -38,7 +38,8 @@ from kamaki.cli.errors import raiseCLIError, CLISyntaxError
 from kamaki.clients.cyclades import CycladesClient, ClientError
 from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
 from kamaki.cli.argument import ProgressBarArgument, DateArgument, IntArgument
-from kamaki.cli.commands import _command_init, errors, _optional_output_cmd
+from kamaki.cli.commands import _command_init, errors
+from kamaki.cli.commands import _optional_output_cmd, _optional_json
 
 from base64 import b64encode
 from os.path import exists
@@ -80,7 +81,7 @@ class _init_cyclades(_command_init):
 
 
 @command(server_cmds)
-class server_list(_init_cyclades):
+class server_list(_init_cyclades, _optional_json):
     """List Virtual Machines accessible by user"""
 
     __doc__ += about_authentication
@@ -94,8 +95,7 @@ class server_list(_init_cyclades):
         more=FlagArgument(
             'output results in pages (-n to set items per page, default 10)',
             '--more'),
-        enum=FlagArgument('Enumerate results', '--enumerate'),
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
+        enum=FlagArgument('Enumerate results', '--enumerate')
     )
 
     def _make_results_pretty(self, servers):
@@ -118,21 +118,16 @@ class server_list(_init_cyclades):
     @errors.cyclades.date
     def _run(self):
         servers = self.client.list_servers(self['detail'], self['since'])
-        if self['json_output']:
-            print_json(servers)
-            return
-        if self['detail']:
+
+        if self['detail'] and not self['json_output']:
             self._make_results_pretty(servers)
 
+        kwargs = dict(with_enumeration=self['enum'])
         if self['more']:
-            print_items(
-                servers,
-                page_size=self['limit'] if self['limit'] else 10,
-                with_enumeration=self['enum'])
-        else:
-            print_items(
-                servers[:self['limit'] if self['limit'] else len(servers)],
-                with_enumeration=self['enum'])
+            kwargs['page_size'] = self['limit'] if self['limit'] else 10
+        elif self['limit']:
+            servers = servers[:self['limit']]
+        self._print(servers, **kwargs)
 
     def main(self):
         super(self.__class__, self)._run()
@@ -140,7 +135,7 @@ class server_list(_init_cyclades):
 
 
 @command(server_cmds)
-class server_info(_init_cyclades):
+class server_info(_init_cyclades, _optional_json):
     """Detailed information on a Virtual Machine
     Contains:
     - name, id, status, create/update dates
@@ -149,11 +144,7 @@ class server_info(_init_cyclades):
     - hardware flavor and os image ids
     """
 
-    arguments = dict(
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
-    )
-
-    def _print(self, server):
+    def _pretty(self, server):
         addr_dict = {}
         if 'attachments' in server:
             atts = server.pop('attachments')
@@ -173,8 +164,7 @@ class server_info(_init_cyclades):
     @errors.cyclades.connection
     @errors.cyclades.server_id
     def _run(self, server_id):
-        printer = print_json if self['json_output'] else self._print
-        printer(self.client.get_server_details(server_id))
+        self._print(self.client.get_server_details(server_id), self._pretty)
 
     def main(self, server_id):
         super(self.__class__, self)._run()
@@ -216,7 +206,7 @@ class PersonalityArgument(KeyValueArgument):
 
 
 @command(server_cmds)
-class server_create(_init_cyclades):
+class server_create(_init_cyclades, _optional_json):
     """Create a server (aka Virtual Machine)
     Parameters:
     - name: (single quoted text)
@@ -226,9 +216,7 @@ class server_create(_init_cyclades):
 
     arguments = dict(
         personality=PersonalityArgument(
-            (80 * ' ').join(howto_personality),
-            ('-p', '--personality')),
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
+            (80 * ' ').join(howto_personality), ('-p', '--personality'))
     )
 
     @errors.generic.all
@@ -236,12 +224,8 @@ class server_create(_init_cyclades):
     @errors.plankton.id
     @errors.cyclades.flavor_id
     def _run(self, name, flavor_id, image_id):
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.create_server(
-            name,
-            int(flavor_id),
-            image_id,
-            self['personality']))
+        self._print([self.client.create_server(
+            name, int(flavor_id), image_id, self['personality'])])
 
     def main(self, name, flavor_id, image_id):
         super(self.__class__, self)._run()
@@ -332,7 +316,7 @@ class server_shutdown(_init_cyclades, _optional_output_cmd):
 
 
 @command(server_cmds)
-class server_console(_init_cyclades):
+class server_console(_init_cyclades, _optional_json):
     """Get a VNC console to access an existing server (VM)
     Console connection information provided (at least):
     - host: (url or address) a VNC host
@@ -340,16 +324,11 @@ class server_console(_init_cyclades):
     - password: for VNC authorization
     """
 
-    arguments = dict(
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.cyclades.connection
     @errors.cyclades.server_id
     def _run(self, server_id):
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.get_server_console(int(server_id)))
+        self._print([self.client.get_server_console(int(server_id))])
 
     def main(self, server_id):
         super(self.__class__, self)._run()
@@ -399,12 +378,11 @@ class server_firewall_get(_init_cyclades):
 
 
 @command(server_cmds)
-class server_addr(_init_cyclades):
+class server_addr(_init_cyclades, _optional_json):
     """List the addresses of all network interfaces on a server (VM)"""
 
     arguments = dict(
-        enum=FlagArgument('Enumerate results', '--enumerate'),
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
+        enum=FlagArgument('Enumerate results', '--enumerate')
     )
 
     @errors.generic.all
@@ -412,12 +390,8 @@ class server_addr(_init_cyclades):
     @errors.cyclades.server_id
     def _run(self, server_id):
         reply = self.client.list_server_nics(int(server_id))
-        if self['json_output']:
-            print_json(reply)
-        else:
-            print_items(
-                reply,
-                with_enumeration=self['enum'] and len(reply) > 1)
+        self._print(
+            reply, with_enumeration=self['enum'] and len(reply) > 1)
 
     def main(self, server_id):
         super(self.__class__, self)._run()
@@ -430,20 +404,16 @@ class server_metadata(_init_cyclades):
 
 
 @command(server_cmds)
-class server_metadata_list(_init_cyclades):
+class server_metadata_list(_init_cyclades, _optional_json):
     """Get server metadata"""
 
-    arguments = dict(
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.cyclades.connection
     @errors.cyclades.server_id
     @errors.cyclades.metadata
     def _run(self, server_id, key=''):
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.get_server_metadata(int(server_id), key))
+        self._print(
+            [self.client.get_server_metadata(int(server_id), key)], title=())
 
     def main(self, server_id, key=''):
         super(self.__class__, self)._run()
@@ -451,7 +421,7 @@ class server_metadata_list(_init_cyclades):
 
 
 @command(server_cmds)
-class server_metadata_set(_init_cyclades):
+class server_metadata_set(_init_cyclades, _optional_json):
     """Set / update server(VM) metadata
     Metadata should be given in key/value pairs in key=value format
     For example:
@@ -459,16 +429,12 @@ class server_metadata_set(_init_cyclades):
     Old, unreferenced metadata will remain intact
     """
 
-    arguments = dict(
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.cyclades.connection
     @errors.cyclades.server_id
     def _run(self, server_id, keyvals):
+        assert keyvals, 'Please, add some metadata ( key=value)'
         metadata = dict()
-        print('TO ANALYZE:', keyvals)
         for keyval in keyvals:
             k, sep, v = keyval.partition('=')
             if sep and k:
@@ -481,8 +447,9 @@ class server_metadata_set(_init_cyclades):
                         'For example:',
                         '/server metadata set <server id>'
                         'key1=value1 key2=value2'])
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.update_server_metadata(int(server_id), **metadata))
+        self._print(
+            [self.client.update_server_metadata(int(server_id), **metadata)],
+            title=())
 
     def main(self, server_id, *key_equals_val):
         super(self.__class__, self)._run()
@@ -507,19 +474,14 @@ class server_metadata_delete(_init_cyclades, _optional_output_cmd):
 
 
 @command(server_cmds)
-class server_stats(_init_cyclades):
+class server_stats(_init_cyclades, _optional_json):
     """Get server (VM) statistics"""
 
-    arguments = dict(
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.cyclades.connection
     @errors.cyclades.server_id
     def _run(self, server_id):
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.get_server_stats(int(server_id)))
+        self._print([self.client.get_server_stats(int(server_id))])
 
     def main(self, server_id):
         super(self.__class__, self)._run()
@@ -566,7 +528,7 @@ class server_wait(_init_cyclades):
 
 
 @command(flavor_cmds)
-class flavor_list(_init_cyclades):
+class flavor_list(_init_cyclades, _optional_json):
     """List available hardware flavors"""
 
     arguments = dict(
@@ -575,19 +537,15 @@ class flavor_list(_init_cyclades):
         more=FlagArgument(
             'output results in pages (-n to set items per page, default 10)',
             '--more'),
-        enum=FlagArgument('Enumerate results', '--enumerate'),
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
+        enum=FlagArgument('Enumerate results', '--enumerate')
     )
 
     @errors.generic.all
     @errors.cyclades.connection
     def _run(self):
         flavors = self.client.list_flavors(self['detail'])
-        if self['json_output']:
-            print_json(flavors)
-            return
         pg_size = 10 if self['more'] and not self['limit'] else self['limit']
-        print_items(
+        self._print(
             flavors,
             with_redundancy=self['detail'],
             page_size=pg_size,
@@ -599,21 +557,16 @@ class flavor_list(_init_cyclades):
 
 
 @command(flavor_cmds)
-class flavor_info(_init_cyclades):
+class flavor_info(_init_cyclades, _optional_json):
     """Detailed information on a hardware flavor
     To get a list of available flavors and flavor ids, try /flavor list
     """
 
-    arguments = dict(
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.cyclades.connection
     @errors.cyclades.flavor_id
     def _run(self, flavor_id):
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.get_flavor_details(int(flavor_id)))
+        self._print([self.client.get_flavor_details(int(flavor_id))])
 
     def main(self, flavor_id):
         super(self.__class__, self)._run()
@@ -621,15 +574,11 @@ class flavor_info(_init_cyclades):
 
 
 @command(network_cmds)
-class network_info(_init_cyclades):
+class network_info(_init_cyclades, _optional_json):
     """Detailed information on a network
     To get a list of available networks and network ids, try /network list
     """
 
-    arguments = dict(
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
-    )
-
     @classmethod
     def _make_result_pretty(self, net):
         if 'attachments' in net:
@@ -642,11 +591,9 @@ class network_info(_init_cyclades):
     @errors.cyclades.network_id
     def _run(self, network_id):
         network = self.client.get_network_details(int(network_id))
-        if self['json_output']:
-            print_json(network)
-            return
         self._make_result_pretty(network)
-        print_dict(network, exclude=('id'))
+        #print_dict(network, exclude=('id'))
+        self._print(network, print_dict, exclude=('id'))
 
     def main(self, network_id):
         super(self.__class__, self)._run()
@@ -654,7 +601,7 @@ class network_info(_init_cyclades):
 
 
 @command(network_cmds)
-class network_list(_init_cyclades):
+class network_list(_init_cyclades, _optional_json):
     """List networks"""
 
     arguments = dict(
@@ -663,8 +610,7 @@ class network_list(_init_cyclades):
         more=FlagArgument(
             'output results in pages (-n to set items per page, default 10)',
             '--more'),
-        enum=FlagArgument('Enumerate results', '--enumerate'),
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
+        enum=FlagArgument('Enumerate results', '--enumerate')
     )
 
     def _make_results_pretty(self, nets):
@@ -675,21 +621,14 @@ class network_list(_init_cyclades):
     @errors.cyclades.connection
     def _run(self):
         networks = self.client.list_networks(self['detail'])
-        if self['json_output']:
-            print_json(networks)
-            return
         if self['detail']:
             self._make_results_pretty(networks)
+        kwargs = dict(with_enumeration=self['enum'])
         if self['more']:
-            print_items(
-                networks,
-                page_size=self['limit'] or 10, with_enumeration=self['enum'])
+            kwargs['page_size'] = self['limit'] or 10
         elif self['limit']:
-            print_items(
-                networks[:self['limit']],
-                with_enumeration=self['enum'])
-        else:
-            print_items(networks, with_enumeration=self['enum'])
+            networks = networks[:self['limit']]
+        self._print(networks, **kwargs)
 
     def main(self):
         super(self.__class__, self)._run()
@@ -697,7 +636,7 @@ class network_list(_init_cyclades):
 
 
 @command(network_cmds)
-class network_create(_init_cyclades):
+class network_create(_init_cyclades, _optional_json):
     """Create an (unconnected) network"""
 
     arguments = dict(
@@ -708,21 +647,19 @@ class network_create(_init_cyclades):
             'Valid network types are '
             'CUSTOM, IP_LESS_ROUTED, MAC_FILTERED (default), PHYSICAL_VLAN',
             '--with-type',
-            default='MAC_FILTERED'),
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
+            default='MAC_FILTERED')
     )
 
     @errors.generic.all
     @errors.cyclades.connection
     @errors.cyclades.network_max
     def _run(self, name):
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.create_network(
+        self._print([self.client.create_network(
             name,
             cidr=self['cidr'],
             gateway=self['gateway'],
             dhcp=self['dhcp'],
-            type=self['type']))
+            type=self['type'])])
 
     def main(self, name):
         super(self.__class__, self)._run()
index 52d090e..d776837 100644 (file)
@@ -38,7 +38,8 @@ from kamaki.clients.image import ImageClient
 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.commands import _command_init, errors
+from kamaki.cli.commands import _optional_output_cmd, _optional_json
 
 
 image_cmds = CommandTree(
@@ -73,7 +74,7 @@ class _init_image(_command_init):
 
 
 @command(image_cmds)
-class image_list(_init_image):
+class image_list(_init_image, _optional_json):
     """List images accessible by user"""
 
     arguments = dict(
@@ -104,8 +105,7 @@ class image_list(_init_image):
         more=FlagArgument(
             'output results in pages (-n to set items per page, default 10)',
             '--more'),
-        enum=FlagArgument('Enumerate results', '--enumerate'),
-        json_output=FlagArgument('Show results in json', ('-j', '--json'))
+        enum=FlagArgument('Enumerate results', '--enumerate')
     )
 
     def _filtered_by_owner(self, detail, *list_params):
@@ -148,18 +148,13 @@ class image_list(_init_image):
         else:
             images = self.client.list_public(detail, filters, order)
 
-        if self['json_output']:
-            print_json(images)
-            return
         images = self._filtered_by_name(images)
+        kwargs = dict(with_enumeration=self['enum'])
         if self['more']:
-            print_items(
-                images,
-                with_enumeration=self['enum'], page_size=self['limit'] or 10)
+            kwargs['page_size'] = self['limit'] or 10
         elif self['limit']:
-            print_items(images[:self['limit']], with_enumeration=self['enum'])
-        else:
-            print_items(images, with_enumeration=self['enum'])
+            images = images[:self['limit']]
+        self._print(images, **kwargs)
 
     def main(self):
         super(self.__class__, self)._run()
@@ -167,7 +162,7 @@ class image_list(_init_image):
 
 
 @command(image_cmds)
-class image_meta(_init_image):
+class image_meta(_init_image, _optional_json):
     """Get image metadata
     Image metadata include:
     - image file information (location, size, etc.)
@@ -175,16 +170,11 @@ class image_meta(_init_image):
     - image os properties (os, fs, etc.)
     """
 
-    arguments = dict(
-        json_output=FlagArgument('Show results in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.plankton.connection
     @errors.plankton.id
     def _run(self, image_id):
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.get_meta(image_id))
+        self._print([self.client.get_meta(image_id)])
 
     def main(self, image_id):
         super(self.__class__, self)._run()
@@ -192,7 +182,7 @@ class image_meta(_init_image):
 
 
 @command(image_cmds)
-class image_register(_init_image):
+class image_register(_init_image, _optional_json):
     """(Re)Register an image"""
 
     arguments = dict(
@@ -207,11 +197,7 @@ class image_register(_init_image):
             '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'),
-        #update=FlagArgument(
-        #    'update existing image properties',
-        #    ('-u', '--update')),
-        json_output=FlagArgument('Show results in json', ('-j', '--json'))
+        size=IntArgument('set image size', '--size')
     )
 
     @errors.generic.all
@@ -239,11 +225,9 @@ class image_register(_init_image):
                 'size',
                 'is_public']).intersection(self.arguments):
             params[key] = self[key]
+        properties = self['properties']
 
-            properties = self['properties']
-
-        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):
         super(self.__class__, self)._run()
@@ -266,21 +250,13 @@ class image_unregister(_init_image, _optional_output_cmd):
 
 
 @command(image_cmds)
-class image_shared(_init_image):
+class image_shared(_init_image, _optional_json):
     """List images shared by a member"""
 
-    arguments = dict(
-        json_output=FlagArgument('Show results in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.plankton.connection
     def _run(self, member):
-        r = self.client.list_shared(member)
-        if self['json_output']:
-            print_json(r)
-        else:
-            print_items(r, title=('image_id',))
+        self._print(self.client.list_shared(member), title=('image_id',))
 
     def main(self, member):
         super(self.__class__, self)._run()
@@ -293,22 +269,14 @@ class image_members(_init_image):
 
 
 @command(image_cmds)
-class image_members_list(_init_image):
+class image_members_list(_init_image, _optional_json):
     """List members of an image"""
 
-    arguments = dict(
-        json_output=FlagArgument('Show results in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.plankton.connection
     @errors.plankton.id
     def _run(self, image_id):
-        members = self.client.list_members(image_id)
-        if self['json_output']:
-            print_json(members)
-        else:
-            print_items(members, title=('member_id',), with_redundancy=True)
+        self._print(self.client.list_members(image_id), title=('member_id',))
 
     def main(self, image_id):
         super(self.__class__, self)._run()
@@ -369,7 +337,7 @@ class image_compute(_init_cyclades):
 
 
 @command(image_cmds)
-class image_compute_list(_init_cyclades):
+class image_compute_list(_init_cyclades, _optional_json):
     """List images"""
 
     arguments = dict(
@@ -378,8 +346,7 @@ class image_compute_list(_init_cyclades):
         more=FlagArgument(
             'output results in pages (-n to set items per page, default 10)',
             '--more'),
-        enum=FlagArgument('Enumerate results', '--enumerate'),
-        json_output=FlagArgument('Show results in json', ('-j', '--json'))
+        enum=FlagArgument('Enumerate results', '--enumerate')
     )
 
     def _make_results_pretty(self, images):
@@ -391,17 +358,14 @@ class image_compute_list(_init_cyclades):
     @errors.cyclades.connection
     def _run(self):
         images = self.client.list_images(self['detail'])
-        if self['json_output']:
-            print_json(images)
-            return
-        if self['detail']:
+        if self['detail'] and not self['json_output']:
             self._make_results_pretty(images)
+        kwargs = dict(with_enumeration=self['enum'])
         if self['more']:
-            print_items(
-                images,
-                page_size=self['limit'] or 10, with_enumeration=self['enum'])
+            kwargs['page_size'] = self['limit'] or 10
         else:
-            print_items(images[:self['limit']], with_enumeration=self['enum'])
+            images = images[:self['limit']]
+        self._print(images, **kwargs)
 
     def main(self):
         super(self.__class__, self)._run()
@@ -409,24 +373,17 @@ class image_compute_list(_init_cyclades):
 
 
 @command(image_cmds)
-class image_compute_info(_init_cyclades):
+class image_compute_info(_init_cyclades, _optional_json):
     """Get detailed information on an image"""
 
-    arguments = dict(
-        json_output=FlagArgument('Show results in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.cyclades.connection
     @errors.plankton.id
     def _run(self, image_id):
         image = self.client.get_image_details(image_id)
-        if self['json_output']:
-            print_json(image)
-            return
-        if 'metadata' in image:
+        if (not self['json_output']) and 'metadata' in image:
             image['metadata'] = image['metadata']['values']
-        print_dict(image)
+        self._print([image])
 
     def main(self, image_id):
         super(self.__class__, self)._run()
@@ -454,19 +411,14 @@ class image_compute_properties(_init_cyclades):
 
 
 @command(image_cmds)
-class image_compute_properties_list(_init_cyclades):
+class image_compute_properties_list(_init_cyclades, _optional_json):
     """List all image properties"""
 
-    arguments = dict(
-        json_output=FlagArgument('Show results in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.cyclades.connection
     @errors.plankton.id
     def _run(self, image_id):
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.get_image_metadata(image_id))
+        self._print(self.client.get_image_metadata(image_id), print_dict)
 
     def main(self, image_id):
         super(self.__class__, self)._run()
@@ -474,20 +426,15 @@ class image_compute_properties_list(_init_cyclades):
 
 
 @command(image_cmds)
-class image_compute_properties_get(_init_cyclades):
+class image_compute_properties_get(_init_cyclades, _optional_json):
     """Get an image property"""
 
-    arguments = dict(
-        json_output=FlagArgument('Show results in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.cyclades.connection
     @errors.plankton.id
     @errors.plankton.metadata
     def _run(self, image_id, key):
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.get_image_metadata(image_id, key))
+        self._print(self.client.get_image_metadata(image_id, key), print_dict)
 
     def main(self, image_id, key):
         super(self.__class__, self)._run()
@@ -495,20 +442,16 @@ class image_compute_properties_get(_init_cyclades):
 
 
 @command(image_cmds)
-class image_compute_properties_add(_init_cyclades):
+class image_compute_properties_add(_init_cyclades, _optional_json):
     """Add a property to an image"""
 
-    arguments = dict(
-        json_output=FlagArgument('Show results in json', ('-j', '--json'))
-    )
-
     @errors.generic.all
     @errors.cyclades.connection
     @errors.plankton.id
     @errors.plankton.metadata
     def _run(self, image_id, key, val):
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.create_image_metadata(image_id, key, val))
+        self._print(
+            self.client.create_image_metadata(image_id, key, val), print_dict)
 
     def main(self, image_id, key, val):
         super(self.__class__, self)._run()
@@ -516,25 +459,22 @@ class image_compute_properties_add(_init_cyclades):
 
 
 @command(image_cmds)
-class image_compute_properties_set(_init_cyclades):
+class image_compute_properties_set(_init_cyclades, _optional_json):
     """Add / update a set of properties for an image
     proeprties must be given in the form key=value, e.v.
     /image compute properties set <image-id> key1=val1 key2=val2
     """
-    arguments = dict(
-        json_output=FlagArgument('Show results in json', ('-j', '--json'))
-    )
 
     @errors.generic.all
     @errors.cyclades.connection
     @errors.plankton.id
     def _run(self, image_id, keyvals):
-        metadata = dict()
+        meta = dict()
         for keyval in keyvals:
             key, val = keyval.split('=')
-            metadata[key] = val
-        printer = print_json if self['json_output'] else print_dict
-        printer(self.client.update_image_metadata(image_id, **metadata))
+            meta[key] = val
+        self._print(
+            self.client.update_image_metadata(image_id, **meta), print_dict)
 
     def main(self, image_id, *key_equals_value):
         super(self.__class__, self)._run()
index 1e9be0d..daea2cb 100644 (file)
@@ -39,12 +39,13 @@ from kamaki.cli import command
 from kamaki.cli.command_tree import CommandTree
 from kamaki.cli.errors import raiseCLIError, CLISyntaxError
 from kamaki.cli.utils import (
-    format_size, to_bytes, print_dict, print_items, pretty_keys,
+    format_size, to_bytes, print_dict, print_items, pretty_keys, pretty_dict,
     page_hold, bold, ask_user, get_path_size, print_json)
 from kamaki.cli.argument import FlagArgument, ValueArgument, IntArgument
 from kamaki.cli.argument import KeyValueArgument, DateArgument
 from kamaki.cli.argument import ProgressBarArgument
-from kamaki.cli.commands import _command_init, errors, _optional_output_cmd
+from kamaki.cli.commands import _command_init, errors
+from kamaki.cli.commands import _optional_output_cmd, _optional_json
 from kamaki.clients.pithos import PithosClient, ClientError
 from kamaki.clients.astakos import AstakosClient
 
@@ -290,7 +291,7 @@ class _file_container_command(_file_account_command):
 
 
 @command(pithos_cmds)
-class file_list(_file_container_command):
+class file_list(_file_container_command, _optional_json):
     """List containers, object trees or objects in a directory
     Use with:
     1 no parameters : containers in current account
@@ -329,8 +330,7 @@ class file_list(_file_container_command):
         exact_match=FlagArgument(
             'Show only objects that match exactly with path',
             '--exact-match'),
-        enum=FlagArgument('Enumerate results', '--enumerate'),
-        json_output=FlagArgument('show output in json', ('-j', '--json'))
+        enum=FlagArgument('Enumerate results', '--enumerate')
     )
 
     def print_objects(self, object_list):
@@ -407,7 +407,7 @@ class file_list(_file_container_command):
                 if_unmodified_since=self['if_unmodified_since'],
                 until=self['until'],
                 show_only_shared=self['shared'])
-            self.print_containers(r.json)
+            self._print(r.json, self.print_containers)
         else:
             prefix = self.path or self['prefix']
             r = self.client.container_get(
@@ -421,7 +421,7 @@ class file_list(_file_container_command):
                 until=self['until'],
                 meta=self['meta'],
                 show_only_shared=self['shared'])
-            self.print_objects(r.json)
+            self._print(r.json, self.print_objects)
 
     def main(self, container____path__=None):
         super(self.__class__, self)._run(container____path__)
@@ -1418,24 +1418,19 @@ class file_download(_file_container_command):
 
 
 @command(pithos_cmds)
-class file_hashmap(_file_container_command):
+class file_hashmap(_file_container_command, _optional_json):
     """Get the hash-map of an object"""
 
     arguments = dict(
         if_match=ValueArgument('show output if ETags match', '--if-match'),
         if_none_match=ValueArgument(
-            'show output if ETags match',
-            '--if-none-match'),
+            'show output if ETags match', '--if-none-match'),
         if_modified_since=DateArgument(
-            'show output modified since then',
-            '--if-modified-since'),
+            'show output modified since then', '--if-modified-since'),
         if_unmodified_since=DateArgument(
-            'show output unmodified since then',
-            '--if-unmodified-since'),
+            'show output unmodified since then', '--if-unmodified-since'),
         object_version=ValueArgument(
-            'get the specific version',
-            ('-O', '--object-version')),
-        json_output=FlagArgument('show headers in json', ('-j', '--json'))
+            'get the specific version', ('-O', '--object-version'))
     )
 
     @errors.generic.all
@@ -1443,15 +1438,13 @@ class file_hashmap(_file_container_command):
     @errors.pithos.container
     @errors.pithos.object_path
     def _run(self):
-        data = self.client.get_object_hashmap(
+        self._print(self.client.get_object_hashmap(
             self.path,
             version=self['object_version'],
             if_match=self['if_match'],
             if_none_match=self['if_none_match'],
             if_modified_since=self['if_modified_since'],
-            if_unmodified_since=self['if_unmodified_since'])
-        printer = print_json if self['json_output'] else print_dict
-        printer(data)
+            if_unmodified_since=self['if_unmodified_since']), print_dict)
 
     def main(self, container___path):
         super(self.__class__, self)._run(
@@ -1614,8 +1607,20 @@ class file_permissions(_pithos_init):
     """
 
 
+def print_permissions(permissions_dict):
+    expected_keys = ('read', 'write')
+    if set(permissions_dict).issubset(expected_keys):
+        print_dict(permissions_dict)
+    else:
+        invalid_keys = set(permissions_dict.keys()).difference(expected_keys)
+        raiseCLIError(
+            'Illegal permission keys: %s' % ', '.join(invalid_keys),
+            importance=1, details=[
+                'Valid permission types: %s' % ' '.join(expected_keys)])
+
+
 @command(pithos_cmds)
-class file_permissions_get(_file_container_command):
+class file_permissions_get(_file_container_command, _optional_json):
     """Get read and write permissions of an object"""
 
     @errors.generic.all
@@ -1623,8 +1628,8 @@ class file_permissions_get(_file_container_command):
     @errors.pithos.container
     @errors.pithos.object_path
     def _run(self):
-        r = self.client.get_object_sharing(self.path)
-        print_dict(r)
+        self._print(
+            self.client.get_object_sharing(self.path), print_permissions)
 
     def main(self, container___path):
         super(self.__class__, self)._run(
@@ -1697,7 +1702,7 @@ class file_permissions_delete(_file_container_command, _optional_output_cmd):
 
 
 @command(pithos_cmds)
-class file_info(_file_container_command):
+class file_info(_file_container_command, _optional_json):
     """Get detailed information for user account, containers or objects
     to get account info:    /file info
     to get container info:  /file info <container>
@@ -1707,8 +1712,7 @@ class file_info(_file_container_command):
     arguments = dict(
         object_version=ValueArgument(
             'show specific version \ (applies only for objects)',
-            ('-O', '--object-version')),
-        json_output=FlagArgument('show headers in json', ('-j', '--json'))
+            ('-O', '--object-version'))
     )
 
     @errors.generic.all
@@ -1724,8 +1728,7 @@ class file_info(_file_container_command):
             r = self.client.get_object_info(
                 self.path,
                 version=self['object_version'])
-        printer = print_json if self['json_output'] else print_dict
-        printer(r)
+        self._print(r, print_dict)
 
     def main(self, container____path__=None):
         super(self.__class__, self)._run(container____path__)
@@ -1740,7 +1743,7 @@ class file_metadata(_pithos_init):
 
 
 @command(pithos_cmds)
-class file_metadata_get(_file_container_command):
+class file_metadata_get(_file_container_command, _optional_json):
     """Get metadata for account, containers or objects"""
 
     arguments = dict(
@@ -1748,8 +1751,7 @@ class file_metadata_get(_file_container_command):
         until=DateArgument('show metadata until then', '--until'),
         object_version=ValueArgument(
             'show specific version \ (applies only for objects)',
-            ('-O', '--object-version')),
-        json_output=FlagArgument('show headers in json', ('-j', '--json'))
+            ('-O', '--object-version'))
     )
 
     @errors.generic.all
@@ -1758,13 +1760,13 @@ class file_metadata_get(_file_container_command):
     @errors.pithos.object_path
     def _run(self):
         until = self['until']
+        r = None
         if self.container is None:
             if self['detail']:
                 r = self.client.get_account_info(until=until)
             else:
                 r = self.client.get_account_meta(until=until)
                 r = pretty_keys(r, '-')
-            print(bold(self.client.account))
         elif self.path is None:
             if self['detail']:
                 r = self.client.get_container_info(until=until)
@@ -1787,8 +1789,7 @@ class file_metadata_get(_file_container_command):
                     version=self['object_version'])
                 r = pretty_keys(pretty_keys(r, '-'))
         if r:
-            printer = print_json if self['json_output'] else print_dict
-            printer(r)
+            self._print(r, print_dict)
 
     def main(self, container____path__=None):
         super(self.__class__, self)._run(container____path__)
@@ -1844,7 +1845,7 @@ class file_metadata_delete(_file_container_command, _optional_output_cmd):
 
 
 @command(pithos_cmds)
-class file_quota(_file_account_command):
+class file_quota(_file_account_command, _optional_json):
     """Get account quota"""
 
     arguments = dict(
@@ -1854,11 +1855,14 @@ class file_quota(_file_account_command):
     @errors.generic.all
     @errors.pithos.connection
     def _run(self):
-        reply = self.client.get_account_quota()
-        if not self['in_bytes']:
-            for k in reply:
-                reply[k] = format_size(reply[k])
-        print_dict(pretty_keys(reply, '-'))
+
+        def pretty_print(output):
+            if not self['in_bytes']:
+                for k in output:
+                    output[k] = format_size(output[k])
+            pretty_dict(output, '-')
+
+        self._print(self.client.get_account_quota(), pretty_print)
 
     def main(self, custom_uuid=None):
         super(self.__class__, self)._run(custom_account=custom_uuid)
@@ -1871,7 +1875,7 @@ class file_containerlimit(_pithos_init):
 
 
 @command(pithos_cmds)
-class file_containerlimit_get(_file_container_command):
+class file_containerlimit_get(_file_container_command, _optional_json):
     """Get container size limit"""
 
     arguments = dict(
@@ -1881,11 +1885,15 @@ class file_containerlimit_get(_file_container_command):
     @errors.generic.all
     @errors.pithos.container
     def _run(self):
-        reply = self.client.get_container_limit(self.container)
-        if not self['in_bytes']:
-            for k, v in reply.items():
-                reply[k] = 'unlimited' if '0' == v else format_size(v)
-        print_dict(pretty_keys(reply, '-'))
+
+        def pretty_print(output):
+            if not self['in_bytes']:
+                for k, v in output.items():
+                    output[k] = 'unlimited' if '0' == v else format_size(v)
+            pretty_dict(output, '-')
+
+        self._print(
+            self.client.get_container_limit(self.container), pretty_print)
 
     def main(self, container=None):
         super(self.__class__, self)._run()
@@ -1894,7 +1902,7 @@ class file_containerlimit_get(_file_container_command):
 
 
 @command(pithos_cmds)
-class file_containerlimit_set(_file_account_command):
+class file_containerlimit_set(_file_account_command, _optional_output_cmd):
     """Set new storage limit for a container
     By default, the limit is set in bytes
     Users may specify a different unit, e.g:
@@ -1948,18 +1956,21 @@ class file_versioning(_pithos_init):
 
 
 @command(pithos_cmds)
-class file_versioning_get(_file_account_command):
+class file_versioning_get(_file_account_command, _optional_json):
     """Get  versioning for account or container"""
 
     @errors.generic.all
     @errors.pithos.connection
     @errors.pithos.container
     def _run(self):
-        if self.container:
-            r = self.client.get_container_versioning(self.container)
-        else:
-            r = self.client.get_account_versioning()
-        print_dict(r)
+        #if self.container:
+        #    r = self.client.get_container_versioning(self.container)
+        #else:
+        #    r = self.client.get_account_versioning()
+        self._print(
+            self.client.get_container_versioning(self.container) if (
+                self.container) else self.client.get_account_versioning(),
+            print_dict)
 
     def main(self, container=None):
         super(self.__class__, self)._run()
@@ -1999,14 +2010,13 @@ class file_group(_pithos_init):
 
 
 @command(pithos_cmds)
-class file_group_get(_file_account_command):
-    """Get groups and group members"""
+class file_group_list(_file_account_command, _optional_json):
+    """list all groups and group members"""
 
     @errors.generic.all
     @errors.pithos.connection
     def _run(self):
-        r = self.client.get_account_group()
-        print_dict(pretty_keys(r, '-'))
+        self._print(self.client.get_account_group(), pretty_dict, delim='-')
 
     def main(self):
         super(self.__class__, self)._run()
@@ -2045,7 +2055,7 @@ class file_group_delete(_file_account_command, _optional_output_cmd):
 
 
 @command(pithos_cmds)
-class file_sharers(_file_account_command):
+class file_sharers(_file_account_command, _optional_json):
     """List the accounts that share objects with current user"""
 
     arguments = dict(
@@ -2057,18 +2067,24 @@ class file_sharers(_file_account_command):
     @errors.pithos.connection
     def _run(self):
         accounts = self.client.get_sharing_accounts(marker=self['marker'])
-        if self['detail']:
-            print_items(accounts)
+        if self['json_output'] or self['detail']:
+            self._print(accounts)
         else:
-            print_items([acc['name'] for acc in accounts])
+            self._print([acc['name'] for acc in accounts])
 
     def main(self):
         super(self.__class__, self)._run()
         self._run()
 
 
+def version_print(versions):
+    print_items([dict(id=vitem[0], created=strftime(
+        '%d-%m-%Y %H:%M:%S',
+        localtime(float(vitem[1])))) for vitem in versions])
+
+
 @command(pithos_cmds)
-class file_versions(_file_container_command):
+class file_versions(_file_container_command, _optional_json):
     """Get the list of object versions
     Deleted objects may still have versions that can be used to restore it and
     get information about its previous state.
@@ -2082,10 +2098,8 @@ class file_versions(_file_container_command):
     @errors.pithos.container
     @errors.pithos.object_path
     def _run(self):
-        versions = self.client.get_object_versionlist(self.path)
-        print_items([dict(id=vitem[0], created=strftime(
-            '%d-%m-%Y %H:%M:%S',
-            localtime(float(vitem[1])))) for vitem in versions])
+        self._print(
+            self.client.get_object_versionlist(self.path), version_print)
 
     def main(self, container___path):
         super(file_versions, self)._run(
index e49b5f6..0564848 100644 (file)
@@ -109,6 +109,10 @@ def print_json(data):
     print(dumps(data, indent=2))
 
 
+def pretty_dict(d, *args, **kwargs):
+    print_dict(pretty_keys(d, *args, **kwargs))
+
+
 def print_dict(
         d, exclude=(), ident=0,
         with_enumeration=False, recursive_enumeration=False):
index ef5d958..c74f845 100644 (file)
@@ -31,7 +31,7 @@
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-from urllib2 import quote
+from urllib2 import quote, unquote
 from urlparse import urlparse
 from threading import Thread
 from json import dumps, loads
@@ -212,13 +212,14 @@ class ResponseManager(Logged):
                 recvlog.info('\n%s <-- %s <-- [req: %s]\n' % (
                     self, r, self.request))
                 self._request_performed = True
-                self._status_code, self._status = r.status, r.reason
+                self._status_code, self._status = r.status, unquote(r.reason)
                 recvlog.info(
                     '%d %s\t[p: %s]' % (self.status_code, self.status, self))
                 self._headers = dict()
                 for k, v in r.getheaders():
                     if (not self.LOG_TOKEN) and k.lower() == 'x-auth-token':
                         continue
+                    v = unquote(v)
                     self._headers[k] = v
                     recvlog.info('  %s: %s\t[p: %s]' % (k, v, self))
                 self._content = r.read()
index abd35b4..bb36a37 100644 (file)
@@ -1116,6 +1116,8 @@ class PithosClient(PithosRestClient):
     def del_container_meta(self, metakey):
         """
         :param metakey: (str) metadatum key
+
+        :returns: (dict) response headers
         """
         r = self.container_post(update=True, metadata={metakey: ''})
         return r.headers