From: Stavros Sachtouris Date: Wed, 22 May 2013 14:26:59 +0000 (+0300) Subject: Implement an optional json output 4 outputing cmds X-Git-Tag: 0.9rc1~9^2~2 X-Git-Url: https://code.grnet.gr/git/kamaki/commitdiff_plain/545c6c29e2f0c0ca809f4ed045f895122c28b904?hp=6dfd55cb34ac6a47e68eaaa6cbe85617fdd1242a Implement an optional json output 4 outputing cmds Refs: #3732 --- diff --git a/Changelog b/Changelog index a88feb4..e0a13aa 100644 --- 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] diff --git a/docs/commands.rst b/docs/commands.rst index 75128fc..b0dae5a 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -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]] diff --git a/docs/man/kamaki.rst b/docs/man/kamaki.rst index 85de2da..0eb9d36 100644 --- a/docs/man/kamaki.rst +++ b/docs/man/kamaki.rst @@ -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]] diff --git a/kamaki/cli/commands/__init__.py b/kamaki/cli/commands/__init__.py index e2255bd..da596bf 100644 --- a/kamaki/cli/commands/__init__.py +++ b/kamaki/cli/commands/__init__.py @@ -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) diff --git a/kamaki/cli/commands/astakos.py b/kamaki/cli/commands/astakos.py index e86075f..81c9ff7 100644 --- a/kamaki/cli/commands/astakos.py +++ b/kamaki/cli/commands/astakos.py @@ -33,10 +33,8 @@ 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) diff --git a/kamaki/cli/commands/cyclades.py b/kamaki/cli/commands/cyclades.py index 9b4b02e..939e691 100644 --- a/kamaki/cli/commands/cyclades.py +++ b/kamaki/cli/commands/cyclades.py @@ -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 ' '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() diff --git a/kamaki/cli/commands/image.py b/kamaki/cli/commands/image.py index 52d090e..d776837 100644 --- a/kamaki/cli/commands/image.py +++ b/kamaki/cli/commands/image.py @@ -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 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() diff --git a/kamaki/cli/commands/pithos.py b/kamaki/cli/commands/pithos.py index 1e9be0d..daea2cb 100644 --- a/kamaki/cli/commands/pithos.py +++ b/kamaki/cli/commands/pithos.py @@ -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 @@ -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( diff --git a/kamaki/cli/utils.py b/kamaki/cli/utils.py index e49b5f6..0564848 100644 --- a/kamaki/cli/utils.py +++ b/kamaki/cli/utils.py @@ -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): diff --git a/kamaki/clients/__init__.py b/kamaki/clients/__init__.py index ef5d958..c74f845 100644 --- a/kamaki/clients/__init__.py +++ b/kamaki/clients/__init__.py @@ -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() diff --git a/kamaki/clients/pithos/__init__.py b/kamaki/clients/pithos/__init__.py index abd35b4..bb36a37 100644 --- a/kamaki/clients/pithos/__init__.py +++ b/kamaki/clients/pithos/__init__.py @@ -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