Commit 117ca598 authored by Stavros Sachtouris's avatar Stavros Sachtouris
Browse files

Merge branch 'feature-json-output' into develop

Conflicts:
	Changelog
	kamaki/cli/commands/image.py

Also, take care of pep8 issues
parents 6aadd6e1 d9301a7a
......@@ -8,6 +8,7 @@ Bug Fixes:
- Allow copy of deleted objects by refering to older version [#3737]
- Add image.add_member missing content-length header
- Do not unpublish by default in all pithos low level requests [#3780]
- Unquote http respons headers
Changes:
......@@ -20,7 +21,7 @@ Changes:
- Disallow moving deleted objects by version [#3737]
This operation was implemented by accident, due to the symetry between
move and copy
- Rename file-meta commands to file-metadata
- Rename file/server-meta commands to file/server-metadata
- Rename image-[add|del]member commands to members-[add|delete]
- Remove update option from imagre-register
- In image-compute split properties to properties-list and properties-get
......@@ -34,11 +35,23 @@ Changes:
unregister, members add/delete/set
-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]
......@@ -58,4 +71,5 @@ Features:
- Store image properties on remote location after image registration [#3769]
- Add runtime args to image register for forcing or unsettitng property
storage [#3769]
- Add server-firewall-get command to get a VMs firewall profile
......@@ -137,19 +137,22 @@ server (Compute/Cyclades)
.. code-block:: text
addmeta : Add server metadata
addr : List a server's nic address
console : Get a VNC console
create : Create a server
delete : Delete a server
delmeta : Delete server metadata
firewall: Set the server's firewall profile
firewall: Manage server's firewall profile
set : Set the server's firewall profile
get : Get the server's firewall profile
info : Get server details
list : List servers
metadata: Manage Server Metadata
list : List server metadata
set : Add / update server metadata
delete: Delete a piece of server metadata
meta : Get a server's metadata
reboot : Reboot a server
rename : Update a server's name
setmeta : Update server's metadata
shutdown: Shutdown a server
start : Start a server
stats : Get server statistics
......@@ -323,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]]
......
......@@ -116,14 +116,17 @@ server commands
* console Get a VNC console to access an existing server (VM)
* create Create a server (aka Virtual Machine)
* delete Delete a server (VM)
* delmeta Delete server (VM) metadata
* firewall Set the server (VM) firewall profile on VMs public network
* firewall Set the server (VM) firewall profile for public networks
* set Set the firewall profile
* get Get the firewall profile
* info Detailed information on a Virtual Machine
* list List Virtual Machines accessible by user
* meta Get a server's metadatum
* metadata Manage a server metadata
* list List server metadata
* set Add or update server metadata
* delete Delete a piece of server metadata
* reboot Reboot a server (VM)
* rename Set/update a server (VM) name
* setmeta set server (VM) metadata
* shutdown Shutdown an active server (VM)
* start Start an existing server (VM)
* stats Get server (VM) statistics
......@@ -188,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]]
......
......@@ -146,19 +146,21 @@ To see the command groups, use -h or --help like in example 1.3.1. In the same w
Options:
- - - -
addmeta : Add server metadata
addr : List a server's nic address
console : Get a VNC console
create : Create a server
delete : Delete a server
delmeta : Delete server metadata
firewall: Set the server's firewall profile
firewall: Manage the server's firewall profile
set: Set the server's firewall profile
get: Get the server's firewall profile
info : Get server details
list : List servers
meta : Get a server's metadata
metadata: Manage server metadata
list : Get a server metadata
set : Add or update server metadata
delete: Delete a piece of server metadata
reboot : Reboot a server
rename : Update a server's name
setmeta : Update server's metadata
shutdown: Shutdown a server
start : Start a server
stats : Get server statistics
......
......@@ -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)
......@@ -33,8 +33,7 @@
from kamaki.cli import command
from kamaki.clients.astakos import AstakosClient
from kamaki.cli.utils import print_dict
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
user_cmds = CommandTree('user', 'Astakos API commands')
......@@ -59,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:
......@@ -72,8 +71,9 @@ class user_authenticate(_user_init):
@errors.user.authenticate
def _run(self, custom_token=None):
super(self.__class__, self)._run()
reply = self.client.authenticate(custom_token)
print_dict(reply)
self._print(
[self.client.authenticate(custom_token)],
title=('uuid', 'name',), with_redundancy=True)
def main(self, custom_token=None):
self._run(custom_token)
......@@ -33,12 +33,13 @@
from kamaki.cli import command
from kamaki.cli.command_tree import CommandTree
from kamaki.cli.utils import print_dict, print_list, print_items
from kamaki.cli.utils import print_dict
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
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
......@@ -117,18 +118,16 @@ class server_list(_init_cyclades):
@errors.cyclades.date
def _run(self):
servers = self.client.list_servers(self['detail'], self['since'])
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()
......@@ -136,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
......@@ -145,7 +144,7 @@ class server_info(_init_cyclades):
- hardware flavor and os image ids
"""
def _print(self, server):
def _pretty(self, server):
addr_dict = {}
if 'attachments' in server:
atts = server.pop('attachments')
......@@ -165,8 +164,7 @@ class server_info(_init_cyclades):
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id):
server = self.client.get_server_details(server_id)
self._print(server)
self._print(self.client.get_server_details(server_id), self._pretty)
def main(self, server_id):
super(self.__class__, self)._run()
......@@ -208,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)
......@@ -218,8 +216,7 @@ class server_create(_init_cyclades):
arguments = dict(
personality=PersonalityArgument(
' /// '.join(howto_personality),
('-p', '--personality'))
(80 * ' ').join(howto_personality), ('-p', '--personality'))
)
@errors.generic.all
......@@ -227,12 +224,8 @@ class server_create(_init_cyclades):
@errors.plankton.id
@errors.cyclades.flavor_id
def _run(self, name, flavor_id, image_id):
r = self.client.create_server(
name,
int(flavor_id),
image_id,
self['personality'])
print_dict(r)
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()
......@@ -240,7 +233,7 @@ class server_create(_init_cyclades):
@command(server_cmds)
class server_rename(_init_cyclades):
class server_rename(_init_cyclades, _optional_output_cmd):
"""Set/update a server (VM) name
VM names are not unique, therefore multiple servers may share the same name
"""
......@@ -249,7 +242,8 @@ class server_rename(_init_cyclades):
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id, new_name):
self.client.update_server_name(int(server_id), new_name)
self._optional_output(
self.client.update_server_name(int(server_id), new_name))
def main(self, server_id, new_name):
super(self.__class__, self)._run()
......@@ -257,14 +251,14 @@ class server_rename(_init_cyclades):
@command(server_cmds)
class server_delete(_init_cyclades):
class server_delete(_init_cyclades, _optional_output_cmd):
"""Delete a server (VM)"""
@errors.generic.all
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id):
self.client.delete_server(int(server_id))
self._optional_output(self.client.delete_server(int(server_id)))
def main(self, server_id):
super(self.__class__, self)._run()
......@@ -272,7 +266,7 @@ class server_delete(_init_cyclades):
@command(server_cmds)
class server_reboot(_init_cyclades):
class server_reboot(_init_cyclades, _optional_output_cmd):
"""Reboot a server (VM)"""
arguments = dict(
......@@ -283,7 +277,8 @@ class server_reboot(_init_cyclades):
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id):
self.client.reboot_server(int(server_id), self['hard'])
self._optional_output(
self.client.reboot_server(int(server_id), self['hard']))
def main(self, server_id):
super(self.__class__, self)._run()
......@@ -291,14 +286,14 @@ class server_reboot(_init_cyclades):
@command(server_cmds)
class server_start(_init_cyclades):
class server_start(_init_cyclades, _optional_output_cmd):
"""Start an existing server (VM)"""
@errors.generic.all
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id):
self.client.start_server(int(server_id))
self._optional_output(self.client.start_server(int(server_id)))
def main(self, server_id):
super(self.__class__, self)._run()
......@@ -306,14 +301,14 @@ class server_start(_init_cyclades):
@command(server_cmds)
class server_shutdown(_init_cyclades):
class server_shutdown(_init_cyclades, _optional_output_cmd):
"""Shutdown an active server (VM)"""
@errors.generic.all
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id):
self.client.shutdown_server(int(server_id))
self._optional_output(self.client.shutdown_server(int(server_id)))
def main(self, server_id):
super(self.__class__, self)._run()
......@@ -321,7 +316,7 @@ class server_shutdown(_init_cyclades):
@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
......@@ -333,8 +328,7 @@ class server_console(_init_cyclades):
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id):
r = self.client.get_server_console(int(server_id))
print_dict(r)
self._print([self.client.get_server_console(int(server_id))])
def main(self, server_id):
super(self.__class__, self)._run()
......@@ -343,6 +337,11 @@ class server_console(_init_cyclades):
@command(server_cmds)
class server_firewall(_init_cyclades):
"""Manage server (VM) firewall profiles for public networks"""
@command(server_cmds)
class server_firewall_set(_init_cyclades, _optional_output_cmd):
"""Set the server (VM) firewall profile on VMs public network
Values for profile:
- DISABLED: Shutdown firewall
......@@ -355,9 +354,8 @@ class server_firewall(_init_cyclades):
@errors.cyclades.server_id
@errors.cyclades.firewall
def _run(self, server_id, profile):
self.client.set_firewall_profile(
server_id=int(server_id),
profile=('%s' % profile).upper())
self._optional_output(self.client.set_firewall_profile(
server_id=int(server_id), profile=('%s' % profile).upper()))
def main(self, server_id, profile):
super(self.__class__, self)._run()
......@@ -365,15 +363,35 @@ class server_firewall(_init_cyclades):
@command(server_cmds)
class server_addr(_init_cyclades):
class server_firewall_get(_init_cyclades):
"""Get the server (VM) firewall profile for its public network"""
@errors.generic.all
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id):
print(self.client.get_firewall_profile(server_id))
def main(self, server_id):
super(self.__class__, self)._run()
self._run(server_id=server_id)
@command(server_cmds)
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')
)
@errors.generic.all
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id):
reply = self.client.list_server_nics(int(server_id))
print_list(reply, with_enumeration=len(reply) > 1)
self._print(
reply, with_enumeration=self['enum'] and len(reply) > 1)
def main(self, server_id):
super(self.__class__, self)._run()
......@@ -381,18 +399,21 @@ class server_addr(_init_cyclades):
@command(server_cmds)
class server_meta(_init_cyclades):
"""Get a server's metadatum
Metadata are formed as key:value pairs where key is used to retrieve them
"""
class server_metadata(_init_cyclades):
"""Manage Server metadata (key:value pairs of server attributes)"""
@command(server_cmds)
class server_metadata_list(_init_cyclades, _optional_json):
"""Get server metadata"""
@errors.generic.all
@errors.cyclades.connection
@errors.cyclades.server_id
@errors.cyclades.metadata
def _run(self, server_id, key=''):
r = self.client.get_server_metadata(int(server_id), key)
print_dict(r)
self._print(
[self.client.get_server_metadata(int(server_id), key)], title=())
def main(self, server_id, key=''):
super(self.__class__, self)._run()
......@@ -400,26 +421,43 @@ class server_meta(_init_cyclades):
@command(server_cmds)
class server_setmeta(_init_cyclades):
"""set server (VM) metadata
Metadata are formed as key:value pairs, both needed to set one
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:
/server metadata set <server id> key1=value1 key2=value2
Old, unreferenced metadata will remain intact
"""
@errors.generic.all
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id, key, val):
metadata = {key: val}
r = self.client.update_server_metadata(int(server_id), **metadata)
print_dict(r)
def main(self, server_id, key, val):
def _run(self, server_id, keyvals):
assert keyvals, 'Please, add some metadata ( key=value)'
metadata = dict()
for keyval in keyvals:
k, sep, v = keyval.partition('=')
if sep and k:
metadata[k] = v
else:
raiseCLIError(
'Invalid piece of metadata %s' % keyval,
importance=2, details=[
'Correct metadata format: key=val',
'For example:',
'/server metadata set <server id>'
'key1=value1 key2=value2'])
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()
self._run(server_id=server_id, key=key, val=val)
self._run(server_id=server_id, keyvals=key_equals_val)
@command(server_cmds)
class server_delmeta(_init_cyclades):
class server_metadata_delete(_init_cyclades, _optional_output_cmd):
"""Delete server (VM) metadata"""
@errors.generic.all
......@@ -427,7 +465,8 @@ class server_delmeta(_init_cyclades):
@errors.cyclades.server_id
@errors.cyclades.metadata
def _run(self, server_id, key):
self.client.delete_server_metadata(int(server_id), key)
self._optional_output(
self.client.delete_server_metadata(int(server_id), key))
def main(self, server_id, key):
super(self.__class__, self)._run()
......@@ -435,15 +474,14 @@ class server_delmeta(_init_cyclades):
@command(server_cmds)
class server_stats(_init_cyclades):
class server_stats(_init_cyclades, _optional_json):
"""Get server (VM) statistics"""
@errors.generic.all
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id):
r = self.client.get_server_stats(int(server_id))
print_dict(r, exclude=('serverRef',))
self._print([self.client.get_server_stats(int(server_id))])
def main(self, server_id):
super(self.__class__, self)._run()
......@@ -490,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(
......@@ -507,7 +545,7 @@ class flavor_list(_init_cyclades):
def _run(self):
flavors = self.client.list_flavors(self['detail'])
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,
......@@ -519,7 +557,7 @@ 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
"""
......@@ -528,8 +566,7 @@ class flavor_info(_init_cyclades):
@errors.cyclades.connection
@errors.cyclades.flavor_id
def _run(self, flavor_id):
flavor = self.client.get_flavor_details(int(flavor_id))
print_dict(flavor)
self._print([self.client.get_flavor_details(int(flavor_id))])
def main(self, flavor_id):
super(self.__class__, self)._run()
......@@ -537,7 +574,7 @@ class flavor_info(_init_cyclades):
@command(network_cmds)