Commit 16d7b9ff authored by Stavros Sachtouris's avatar Stavros Sachtouris
Browse files

Update and correct kamaki.clients documentation

parent 97086fcd
...@@ -143,16 +143,16 @@ server (Compute/Cyclades) ...@@ -143,16 +143,16 @@ server (Compute/Cyclades)
.. code-block:: text .. code-block:: text
addr : List a server's nic address addr : List a server nic address
console : Get a VNC console console : Get a VNC console
create : Create a server create : Create a server
delete : Delete a server delete : Delete a server
firewall: Manage server's firewall profile firewall: Manage server firewall profile
set : Set the server's firewall profile set : Set the server firewall profile
get : Get the server's firewall profile get : Get the server firewall profile
ip : Manage floating IPs for the servers ip : Manage floating IPs for the servers
attach: Attach a floating ip to a server with server_id attach: Attach a floating ip to a server with server_id
info : A floating IPs' details info : A floating IP details
detach: Detach floating ip from server detach: Detach floating ip from server
list : List all floating ips list : List all floating ips
create: Create a new floating IP create: Create a new floating IP
...@@ -165,7 +165,7 @@ server (Compute/Cyclades) ...@@ -165,7 +165,7 @@ server (Compute/Cyclades)
set : Add / update server metadata set : Add / update server metadata
delete: Delete a piece of server metadata delete: Delete a piece of server metadata
reboot : Reboot a server reboot : Reboot a server
rename : Update a server's name rename : Update a server name
shutdown: Shutdown a server shutdown: Shutdown a server
start : Start a server start : Start a server
stats : Get server statistics stats : Get server statistics
...@@ -445,12 +445,12 @@ Showcase: Upload and download a file ...@@ -445,12 +445,12 @@ Showcase: Upload and download a file
-rw-rw-r-- 1 ******** ******** 20M Nov 26 15:42 rndm_remote.file -rw-rw-r-- 1 ******** ******** 20M Nov 26 15:42 rndm_remote.file
[file]: !diff rndm_local.file rndm_remote.file [file]: !diff rndm_local.file rndm_remote.file
.. Note:: In kamaki shell, ! is used to execute OS shell commands (e.g. bash) .. Note:: In kamaki shell, ! is used to execute OS shell commands (e.g., bash)
.. warning:: The container:object/path syntax does not function if the .. warning:: The container:object/path syntax does not function if the
container and / or the object path contain one or more : characters. To use container and / or the object path contain one or more : characters. To use
containers and objects with : use the --container and --dst-container containers and objects with : use the --container and --dst-container
arguments, e.g. to copy test.py object from example:dev container to arguments, e.g., to copy test.py object from example:dev container to
example:deploy :: example:deploy ::
$ kamaki file copy --container=example:dev test.py --dst-container=example:deploy $ kamaki file copy --container=example:dev test.py --dst-container=example:deploy
...@@ -2,29 +2,31 @@ Adding Commands ...@@ -2,29 +2,31 @@ Adding Commands
=============== ===============
Kamaki commands are implemented as python classes, decorated with a special Kamaki commands are implemented as python classes, decorated with a special
decorator called *command*. This decorator is a method of kamaki.cli that adds decorator called *command*. This decorator is a method of *kamaki.cli* that
a new command in a CommandTree structure (kamaki.cli.commant_tree). The later adds a new command in a *CommandTree* structure. A *CommandTree* (package
is used by interfaces to manage kamaki commands. *kamaki.cli.commant_tree*) is a data structure used by kamaki to manage command
namespaces.
In the following, a set of kamaki commands will be implemented:: For demonstration purposes, the following set of kamaki commands will be
implemented in this document::
mygrp1 list all //show a list mygrp1 list all //show a list
mygrp1 list details [--match=<>] //show list of details mygrp1 list details [--match=<>] //show list of details
mygrp2 list all [regular expression] [-l] //list all subjects mygrp2 list all [regular expression] [-l] //list all subjects
mygrp2 info <id> [name] //get information for subject with id mygrp2 info <id> [name] //get information for subject with id
There are two command sets to implement, namely mygrp1 and mygrp2. The first There are two command groups to implement i.e., *mygrp1* and *mygrp2*,
will contain two commands, namely list-all and list-details. The second one containing two commands each (*list_all*, *list_details* and *list_all*, *info*
will also contain two commands, list-all and info. To avoid ambiguities, respectively). To avoid ambiguities, command names are prefixed with the
command names should rather be prefixed with the group they belong to, e.g. command group they belong to, e.g., *mygrp1_list_all* and *mygrp2_list_all*.
mygrp1-list-all and mygrp2-list-all. The underscore is used to separate command namespaces.
The first command has the simplest possible syntax: no parameters, no runtime The first command (*mygrp1_list_all*) has the simplest possible syntax: no
arguments. The second accepts an optional runtime argument with a value. The parameters, no runtime arguments. The second accepts an optional runtime argument with a value. The third features an optional parameter and an optional
third features an optional argument and an optional runtime flag argument. The runtime flag argument. The last is an example of a command with an obligatory
last is an example of a command with an obligatory and an optional argument. and an optional parameter.
Samples of the expected behavior in one-command mode are following: Examples of the expected behavior in one-command mode:
.. code-block:: console .. code-block:: console
...@@ -35,34 +37,32 @@ Samples of the expected behavior in one-command mode are following: ...@@ -35,34 +37,32 @@ Samples of the expected behavior in one-command mode are following:
- - - - - - - -
list list
$ kamaki mygrp1 list $ kamaki mygrp1 list
Syntax Error
Options Options
- - - - - - - -
all show a list all show a list
details show a list of details details show a list of details
$ kamaki mygrp1 list all $ kamaki mygrp1 list all
... (mygrp1 client method is called) ... ... (a mygrp1_list_all instance runs) ...
$ kamaki mygrp2 list all 'Z[.]' -l $ kamaki mygrp2 list all 'Z[.]' -l
... (mygrp2 client method is called) ... ... (a mygrp2_list_all instance runs) ...
$ $
The above example will be used throughout the present guide.
The CommandTree structure The CommandTree structure
------------------------- -------------------------
CommandTree manages a command by its path. Each command is stored in multiple CommandTree manages a command by its namespace. Each command is stored in
nodes on the tree, so that the last term is a leaf and the route from root to a tree path, where each node is a name. A leaf is the end term of a namespace and contains a pointer to the command class to be executed.
that leaf represents the command path. For example the commands *file upload*,
*file list* and *file info* are stored together as shown bellow:: Here is an example from the actual kamaki command structure, where the commands
*file upload*, *file list* and *file info* are represented as shown bellow::
- file - file
''''''''|- info ''''''''|- info
|- list |- list
|- upload |- upload
The example used in the present, should result to the creation of two trees:: Now, let's load the showcase example on CommandTrees::
- mygrp1 - mygrp1
''''''''|- list ''''''''|- list
...@@ -74,12 +74,12 @@ The example used in the present, should result to the creation of two trees:: ...@@ -74,12 +74,12 @@ The example used in the present, should result to the creation of two trees::
'''''''|- all '''''''|- all
|- info |- info
Each command group should be stored on a different CommandTree. For that Each command group should be stored on a different CommandTree.
reason, command specification modules should contain a list of CommandTree
objects, named *_commands* For that reason, command specification modules should contain a list of CommandTree objects, named *_commands*. This mechanism allows any interface
application to load the list of commands from the *_commands* array.
A command group information (name, description) is provided at CommandTree The first name of the command path and a description (name, description) are needed to initializeg a CommandTree:
structure initialization:
.. code-block:: python .. code-block:: python
...@@ -88,15 +88,19 @@ structure initialization: ...@@ -88,15 +88,19 @@ structure initialization:
_commands = [_mygrp1_commands, _mygrp2_commands] _commands = [_mygrp1_commands, _mygrp2_commands]
The command decorator The command decorator
--------------------- ---------------------
The *command* decorator mines all the information necessary to build a command All commands are specified by subclasses of *kamaki.cli.commands._command_init*
specification which is then inserted in a CommanTree instance:: These classes are called "command specifications".
The *command* decorator mines all the information needed to build a namespace
from a command specification::
class code ---> command() --> updated CommandTree structure class code ---> command() --> updated CommandTree structure
Kamaki interfaces make use of this CommandTree structure. Optimizations are Kamaki interfaces make use of the CommandTree structure. Optimizations are
possible by using special parameters on the command decorator method. possible by using special parameters on the command decorator method.
.. code-block:: python .. code-block:: python
...@@ -108,14 +112,16 @@ possible by using special parameters on the command decorator method. ...@@ -108,14 +112,16 @@ possible by using special parameters on the command decorator method.
:param prefix: of the commands allowed to be inserted ('' for all) :param prefix: of the commands allowed to be inserted ('' for all)
:param descedants_depth: is the depth of the tree descedants of the :param descedants_depth: is the depth of the tree descendants of the
prefix command. prefix command.
""" """
Creating a new command specification set Creating a new command specification set
---------------------------------------- ----------------------------------------
A command specification developer should create a new module (python file) with as many classes as the command specifications to be offered. Each class should be decorated with *command*. A command specification developer should create a new module (python file) with
one command specification class per command. Each class should be decorated
with *command*.
.. code-block:: python .. code-block:: python
...@@ -129,10 +135,10 @@ A command specification developer should create a new module (python file) with ...@@ -129,10 +135,10 @@ A command specification developer should create a new module (python file) with
... ...
A list of CommandTree structures must exist in the module scope, with the name A list of CommandTree structures must exist in the module scope, with the name
_commands, as shown above. Different CommandTree objects correspond to *_commands*. Different CommandTree objects correspond to different command
different command groups. groups.
Get command description Set command description
----------------------- -----------------------
The description of each command is the first line of the class commend. The The description of each command is the first line of the class commend. The
...@@ -143,25 +149,50 @@ subject with id*" description. ...@@ -143,25 +149,50 @@ subject with id*" description.
... ...
@command(_mygrp2_commands) @command(_mygrp2_commands)
class mygrp2_info() class mygrp2_info():
"""get information for subject with id""" """get information for subject with id
Anything from this point and bellow constitutes the long description
Please, mind the indentation, pep8 is not forgiving.
"""
... ...
Description placeholders
------------------------
There is possible to create an empty command, that can act as a description
placeholder. For example, the *mygrp1_list* namespace does not correspond to an
executable command, but it can have a helpful description. In that case, create
a command specification class with a command and no code:
.. code-block:: python
@command(_mygrp1_commands)
class mygrp1_list():
"""List mygrp1 objects.
There are two versions: short and detailed
"""
.. warning:: A command specification class with no description is invalid and
will cause an error.
Declare run-time argument Declare run-time argument
------------------------- -------------------------
The argument mechanism allows the definition of run-time arguments. Some basic A special argument mechanism allows the definition of run-time arguments. This
argument types are defined at the mechanism is based on argparse and is designed to simplify argument definitions
when specifying commands.
Some basic argument types are defined at the
`argument module <code.html#module-kamaki.cli.argument>`_, but it is not `argument module <code.html#module-kamaki.cli.argument>`_, but it is not
uncommon to extent these classes in order to achieve specialized type checking a bad idea to extent these classes in order to achieve specialized type
and syntax control (e.g. at checking and syntax control. Still, in most cases, the argument types of the
`pithos cli module <code.html#module-kamaki.cli.commands.pithos>`_). argument package are enough for most cases.
To declare a run-time argument on a specific command, the object class should To declare a run-time argument on a specific command, the specification class
initialize a dict called *arguments* , where Argument objects are stored. Each should contain a dict called *arguments* , where Argument objects are stored.
argument object is a possible run-time argument. Syntax checking happens at Each argument object is a run-time argument. Syntax checking happens at client
client level, while the type checking is implemented in the Argument code level, while the type checking is implemented in the Argument code (e.g.,
(thus, many different Argument types might be needed).` IntArgument checks if the value is an int).
.. code-block:: python .. code-block:: python
...@@ -190,15 +221,15 @@ or more usually and elegantly: ...@@ -190,15 +221,15 @@ or more usually and elegantly:
arguments = dict( arguments = dict(
match=ValueArgument( match=ValueArgument(
'Filter output to match string', ('-m', --match')) 'Filter output to match string', ('-m', --match'))
) )
Accessing run-time arguments Accessing run-time arguments
---------------------------- ----------------------------
To access run-time arguments, users can use the _command_init interface, which To access run-time arguments, users can use the *_command_init* interface,
implements __item__ accessors to handle run-time argument values. In specific, which implements *__item__* accessors to handle run-time argument values. In
an instance of _command_init can use brackets to set or read <argument>.value . other words, one may get the value of an argument with *self[<argument>]*.
.. code-block:: python .. code-block:: python
...@@ -222,9 +253,8 @@ an instance of _command_init can use brackets to set or read <argument>.value . ...@@ -222,9 +253,8 @@ an instance of _command_init can use brackets to set or read <argument>.value .
The main method and command parameters The main method and command parameters
-------------------------------------- --------------------------------------
The command behavior for each command / class is coded in *main*. The The command behavior for each command class is coded in *main*. The
parameters of *main* method defines the command parameters part of the syntax. parameters of *main* method affect the syntax of the command. In specific::
In specific::
main(self, param) - obligatory parameter <param> main(self, param) - obligatory parameter <param>
main(self, param=None) - optional parameter [param] main(self, param=None) - optional parameter [param]
...@@ -236,8 +266,8 @@ In specific:: ...@@ -236,8 +266,8 @@ In specific::
main(self, *args) - arbitary number of params [...] main(self, *args) - arbitary number of params [...]
main(self, param1____param2, *args) - <param1:param2> [...] main(self, param1____param2, *args) - <param1:param2> [...]
The information that can be mined by *command* for each individual command is Let's have a look at the command specification class again, and highlight the
presented in the following: parts that affect the command syntax:
.. code-block:: python .. code-block:: python
:linenos: :linenos:
...@@ -245,23 +275,32 @@ presented in the following: ...@@ -245,23 +275,32 @@ presented in the following:
from kamaki.cli.argument import FlagArgument from kamaki.cli.argument import FlagArgument
... ...
_commands = [_mygrp1_commands, _mygrp2=commands] _commands = [_mygrp1_commands, _mygrp2_commands]
... ...
@command(_mygrp2_commands) @command(_mygrp2_commands)
class mygrp2_list_all(): class mygrp2_list_all():
"""List all subjects""" """List all subjects
Refers to the subject accessible by current user
"""
arguments = dict(FlagArgument('detailed list', '-l')) arguments = dict(FlagArgument('detailed list', '-l'))
def main(self, reg_exp=None): def main(self, reg_exp=None):
... ...
This will load the following information on the CommandTree: The above lines contain the following information:
* Namespace and name (line 8): mygrp2 list all
* Short (line 9) and long (line 10) description
* Parameters (line 15): [reg exp]
* Runtime arguments (line 13): [-l]
* Runtime arguments help (line 13): detailed list
* Syntax (from lines 8,12,19): mygrp list all [reg exp] [-l] .. tip:: It is suggested to code the main functionality in a member method
* Description (form line 9): List all subjects called *_run*. This allows the separation between syntax and logic. For
* Arguments help (from line 13,14): -l: detailed list example, an external library may need to call a command without caring
about its command line behavior.
Letting kamaki know Letting kamaki know
------------------- -------------------
...@@ -270,11 +309,11 @@ Kamaki will load a command specification *only* if it is set as a configurable ...@@ -270,11 +309,11 @@ Kamaki will load a command specification *only* if it is set as a configurable
option. To demonstrate this, let the command specifications coded above be option. To demonstrate this, let the command specifications coded above be
stored in a file named *grps.py*. stored in a file named *grps.py*.
The developer should move file *grps.py* to kamaki/cli/commands, the default The developer should move the file *grps.py* to *kamaki/cli/commands*, the
place for command specifications, although running a command specification from default place for command specifications
a different path is also a kamaki feature.
The user has to use a configuration file where the following is added: These lines should be contained in the kamaki configuration file for a new
command specification module to work:
:: ::
[global] [global]
...@@ -288,9 +327,10 @@ or equivalently: ...@@ -288,9 +327,10 @@ or equivalently:
$ kamaki config set mygrp1_cli grps $ kamaki config set mygrp1_cli grps
$ kamaki config set mygrp2_cli grps $ kamaki config set mygrp2_cli grps
Command specification modules don't need to live in kamaki/cli/commands, .. note:: running a command specification from a different path is supported.
although this is suggested for uniformity. If a command module exist in another To achieve this, add a *<group>_cli = </path/to/module>* line in the
path:: configure file under the *global* section.
::
[global] [global]
mygrp_cli = /another/path/grps.py mygrp_cli = /another/path/grps.py
...@@ -319,13 +359,23 @@ Summary: create a command set ...@@ -319,13 +359,23 @@ Summary: create a command set
# Define command specifications # Define command specifications
@command(_mygrp1_commands)
class mygrp1_list(_command_init):
"""List mygrp1 objects.
There are two versions: short and detailed
"""
@command(_mygrp1_commands) @command(_mygrp1_commands)
class mygrp1_list_all(_command_init): class mygrp1_list_all(_command_init):
"""show a list""" """show a list"""
def main(self): def _run():
... ...
def main(self):
self._run()
@command(_mygrp1_commands) @command(_mygrp1_commands)
class mygrp1_list_details(_command_init): class mygrp1_list_details(_command_init):
...@@ -336,11 +386,16 @@ Summary: create a command set ...@@ -336,11 +386,16 @@ Summary: create a command set
'Filter output to match string', ('-m', --match')) 'Filter output to match string', ('-m', --match'))
) )
def main(self): def _run(self):
...
match_value = self['match'] match_value = self['match']
... ...
def main(self):
self._run()
#The following will also create a mygrp2_list command with no description
@command(_mygrp2_commands) @command(_mygrp2_commands)
class mygrp2_list_all(_command_init): class mygrp2_list_all(_command_init):
...@@ -350,21 +405,23 @@ Summary: create a command set ...@@ -350,21 +405,23 @@ Summary: create a command set
list=FlagArgument('detailed listing', '-l') list=FlagArgument('detailed listing', '-l')
) )
def main(self, regular_expression=None): def _run(self, regexp):
...
detail_flag = self['list']
... ...
if detail_flag: if self['list']:
... ...
... else:
if regular_expression:
... ...
...
def main(self, regular_expression=None):
self._run(regular_expression)
@command(_mygrp2_commands) @command(_mygrp2_commands)
class mygrp2_info(_command_init): class mygrp2_info(_command_init):
"""get information for subject with id""" """get information for subject with id"""
def main(self, id, name=''): def _run(self, grp_id, grp_name):
... ...
def main(self, id, name=''):
self._run(id, name)
...@@ -3,28 +3,31 @@ Creating applications with kamaki API ...@@ -3,28 +3,31 @@ Creating applications with kamaki API
Kamaki features a clients API for building third-party client applications that Kamaki features a clients API for building third-party client applications that
communicate with OpenStack and / or Synnefo cloud services. The package is communicate with OpenStack and / or Synnefo cloud services. The package is
called kamaki.clients and servers as a lib. called *kamaki.clients* and serves as a lib.
A showcase of an application built on kamaki.clients is kamaki.cli, the command A showcase of an application built on *kamaki.clients* is *kamaki.cli*, the
line interface of kamaki. command line interface of kamaki.
Since Synnefo services are build as OpenStack extensions, an inheritance Since Synnefo services are build as OpenStack extensions, an inheritance
approach has been chosen for implementing clients for both. In specific, approach has been chosen for implementing clients for both. In specific,
the *compute*, *storage* and *image* modules are clients of the OS compute, OS the *compute*, *storage* and *image* modules are client implementations for the
object-store, respectively. On the contrary, all the other modules are Synnefo OpenStack compute and OpenStack object-store APIs, respectively. The rest of the
extensions (*cyclades* extents *compute*, *pithos* and *pithos_rest_api* modules implement the Synnefo extensions (i.e., *cyclades* and
extent *storage*) or novel Synnefo services (e.g., *astakos* for IM, *image* *cyclades_rest_api* extents *compute*, *pithos* and *pithos_rest_api* extent
for *plankton*). *storage*) or novel Synnefo services (*image* for *plankton*).
Setup a client instance Setup a client instance
----------------------- -----------------------
External applications may instantiate one or more kamaki clients. There is a client for every API, therefore an external applications should
instantiate they kamaki clients they need. For example, to manage virtual
servers and stored objects / files, an application would probably need to
instantiate the CycladesClient and PithosClient respectively.
.. code-block:: python .. code-block:: python
:emphasize-lines: 1 :emphasize-lines: 1