Commit 18555878 authored by Stavros Sachtouris's avatar Stavros Sachtouris
Browse files

Merge branch 'develop' into feature-quota

Conflicts:
	kamaki/cli/commands/history_cli.py
	kamaki/cli/commands/pithos_cli.py
parents 131f4e86 16c895db
......@@ -157,7 +157,7 @@ store commands
* append Append local file to (existing) remote object
* cat Print a file to console
* copy Copy an object
* create Create a container or a directory object
* create Create a container
* delete Delete a container [or an object]
* delgroup Delete a user group on an account
* delmeta Delete an existing metadatum of account [, container [or object]]
......@@ -182,6 +182,7 @@ store commands
* setquota Set new quota (in KB) for account [or container]
* setversioning Set new versioning (auto, none) for account [or container]
* sharers List the accounts that share objects with default account
* touch Create an empty file
* truncate Truncate remote file up to a size
* unpublish Unpublish an object
* upload Upload a file
......
......@@ -38,7 +38,7 @@ from inspect import getargspec
from kamaki.cli.argument import ArgumentParseManager
from kamaki.cli.history import History
from kamaki.cli.utils import print_dict, print_list, red, magenta, yellow
from kamaki.cli.utils import print_dict, red, magenta, yellow
from kamaki.cli.errors import CLIError
_help = False
......
......@@ -35,6 +35,7 @@ from kamaki.cli.config import Config
from kamaki.cli.errors import CLISyntaxError, raiseCLIError
from kamaki.cli.utils import split_input
from logging import getLogger
from datetime import datetime as dtm
from argparse import ArgumentParser, ArgumentError
......@@ -236,6 +237,43 @@ class IntArgument(ValueArgument):
details=['Value %s not an int' % newvalue]))
class DateArgument(ValueArgument):
"""
:value type: a string formated in an acceptable date format
:value returns: same date in first of DATE_FORMATS
"""
DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
"%A, %d-%b-%y %H:%M:%S GMT",
"%a, %d %b %Y %H:%M:%S GMT"]
INPUT_FORMATS = DATE_FORMATS + ["%d-%m-%Y", "%H:%M:%S %d-%m-%Y"]
@property
def value(self):
return getattr(self, '_value', self.default)
@value.setter
def value(self, newvalue):
if newvalue is None:
return
self._value = self.format_date(newvalue)
def format_date(self, datestr):
for format in self.INPUT_FORMATS:
try:
t = dtm.strptime(datestr, format)
except ValueError:
continue
self._value = t.strftime(self.DATE_FORMATS[0])
return
raiseCLIError(None,
'Date Argument Error',
details='%s not a valid date. correct formats:\n\t%s'\
% (datestr, self.INPUT_FORMATS))
class VersionArgument(FlagArgument):
"""A flag argument with that prints current version"""
......
......@@ -100,6 +100,15 @@ class Shell(Cmd):
def set_prompt(self, new_prompt):
self.prompt = '%s%s%s' % (self._prefix, new_prompt, self._suffix)
def cmdloop(self):
while True:
try:
Cmd.cmdloop(self)
except KeyboardInterrupt:
print(' - interrupted')
continue
break
def do_exit(self, line):
print('')
if self.prompt[len(self._prefix):-len(self._suffix)]\
......@@ -170,7 +179,8 @@ class Shell(Cmd):
cls = subcmd.get_class()
ldescr = getattr(cls, 'long_description', '')
if subcmd.path == 'history_run':
instance = cls(dict(cmd_parser.arguments),
instance = cls(
dict(cmd_parser.arguments),
self.cmd_tree)
else:
instance = cls(dict(cmd_parser.arguments))
......@@ -191,10 +201,12 @@ class Shell(Cmd):
arg.value = getattr(cmd_parser.parsed, name,
arg.default)
exec_cmd(instance,
[term for term in cmd_parser.unparsed\
if not term.startswith('-')],
exec_cmd(
instance,
cmd_parser.unparsed,
cmd_parser.parser.print_help)
#[term for term in cmd_parser.unparsed\
# if not term.startswith('-')],
except (ClientError, CLIError) as err:
print_error_message(err)
elif ('-h' in cmd_args or '--help' in cmd_args) \
......@@ -281,6 +293,7 @@ class Shell(Cmd):
try:
self.cmdloop()
except Exception:
except Exception as e:
print('(%s)' % e)
from traceback import print_stack
print_stack()
......@@ -39,11 +39,54 @@ recvlog = logging.getLogger('clients.recv')
class _command_init(object):
def __init__(self, arguments={}):
self.arguments = arguments
if hasattr(self, 'arguments'):
arguments.update(self.arguments)
self.arguments = dict(arguments)
try:
self.config = self.get_argument('config')
self.config = self['config']
#self.config = self.get_argument('config')
except KeyError:
pass
def get_argument(self, argterm):
def __getitem__(self, argterm):
"""
:param argterm: (str) the name/label of an argument in self.arguments
:returns: the value of the corresponding Argument (not the argument
object)
:raises KeyError: if argterm not in self.arguments of this object
"""
return self.arguments[argterm].value
def __setitem__(self, argterm, arg):
"""Install an argument as argterm
If argterm points to another argument, the other argument is lost
:param argterm: (str)
:param arg: (Argument)
"""
if not hasattr(self, 'arguments'):
self.arguments = {}
self.arguments[argterm] = arg
def get_argument_object(self, argterm):
"""
:param argterm: (str) the name/label of an argument in self.arguments
:returns: the arument object
:raises KeyError: if argterm not in self.arguments of this object
"""
return self.arguments[argterm]
def get_argument(self, argterm):
"""
:param argterm: (str) the name/label of an argument in self.arguments
:returns: the value of the arument object
:raises KeyError: if argterm not in self.arguments of this object
"""
return self[argterm]
......@@ -74,6 +74,7 @@ class astakos_authenticate(_astakos_init):
'If not, set a token:',
' 1.(permanent): /config set token <token>',
' 2.(temporary): rerun with <token> parameter'])
raiseCLIError(ce)
except Exception as err:
raiseCLIError(err)
print_dict(reply)
......@@ -39,26 +39,39 @@ from kamaki.cli.command_tree import CommandTree
config_cmds = CommandTree('config', 'Configuration commands')
_commands = [config_cmds]
about_options = '\nAbout options:\
\n. syntax: [group.]option\
\n. example: store.account\
\n. special case: <option> is equivalent to global.<option>\
\n. configuration file syntax:\
\n. [group]\
\n. option=value\
\n. (more options can be set per group)'
@command(config_cmds)
class config_list(_command_init):
"""List configuration options"""
def __init__(self, arguments={}):
super(config_list, self).__init__(arguments)
self.arguments['all'] = FlagArgument('include default values', '-a')
"""List all configuration options
FAQ:
Q: I haven't set any options!
A: Defaults are used (override with /config set )
Q: There are more options than I have set
A: Default options remain if not explicitly replaced or deleted
"""
def main(self):
include_defaults = self.get_argument('all')
for section in sorted(self.config.sections()):
items = self.config.items(section, include_defaults)
items = self.config.items(section)
for key, val in sorted(items):
print('%s.%s = %s' % (section, key, val))
@command(config_cmds)
class config_get(_command_init):
"""Show a configuration option"""
"""Show a configuration option
"""
__doc__ += about_options
def main(self, option):
section, sep, key = option.rpartition('.')
......@@ -72,6 +85,8 @@ class config_get(_command_init):
class config_set(_command_init):
"""Set a configuration option"""
__doc__ += about_options
def main(self, option, value):
section, sep, key = option.rpartition('.')
section = section or 'global'
......@@ -82,11 +97,20 @@ class config_set(_command_init):
@command(config_cmds)
class config_delete(_command_init):
"""Delete a configuration option (and use the default value)"""
"""Delete a configuration option
Default values are not removed by default. To alter this behavior in a
session, use --default.
"""
arguments = dict(
default=FlagArgument(
'Remove default value as well (persists until end of sesion)',
'--default')
)
def main(self, option):
section, sep, key = option.rpartition('.')
section = section or 'global'
self.config.remove_option(section, key)
self.config.remove_option(section, key, self['default'])
self.config.write()
self.config.reload()
This diff is collapsed.
......@@ -80,8 +80,8 @@ class _init_history(_command_init):
class history_show(_init_history):
"""Show intersession command history
---
* With no parameters : pick all commands in history records
* With:
- With no parameters : pick all commands in history records
- With:
. 1. <order-id> : pick the <order-id>th command
. 2. <order-id-1>-<order-id-2> : pick all commands ordered in the range
. [<order-id-1> - <order-id-2>]
......@@ -93,17 +93,14 @@ class history_show(_init_history):
. -5--2 means : the last 5 commands except the last 2
"""
def __init__(self, arguments={}):
super(self.__class__, self).__init__(arguments)
self.arguments['limit'] =\
IntArgument('number of lines to show', '-n', default=0)
self.arguments['match'] =\
ValueArgument('show lines that match all given terms', '--match')
arguments = dict(
limit=IntArgument('number of lines to show', '-n', default=0),
match=ValueArgument('show lines that match given terms', '--match')
)
def main(self, *cmd_ids):
super(self.__class__, self).main()
ret = self.history.get(match_terms=self.get_argument('match'),
limit=self.get_argument('limit'))
ret = self.history.get(match_terms=self['match'], limit=self['limit'])
if not cmd_ids:
print(''.join(ret))
......
......@@ -34,19 +34,24 @@
from kamaki.cli import command
from kamaki.cli.command_tree import CommandTree
from kamaki.cli.errors import raiseCLIError
from kamaki.cli.utils import print_dict, print_items, bold
from kamaki.cli.utils import print_dict, print_items
from kamaki.clients.image import ImageClient, ClientError
from kamaki.cli.argument import\
FlagArgument, ValueArgument, KeyValueArgument, IntArgument
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
from kamaki.cli.argument import IntArgument
from kamaki.cli.commands.cyclades_cli import _init_cyclades
from kamaki.cli.commands.cyclades_cli import raise_if_connection_error
from kamaki.cli.commands import _command_init
image_cmds = CommandTree('image',
image_cmds = CommandTree(
'image',
'Compute/Cyclades or Glance API image commands')
_commands = [image_cmds]
about_image_id = ['To see a list of available image ids: /image list']
class _init_image(_command_init):
def main(self):
try:
......@@ -65,84 +70,108 @@ class _init_image(_command_init):
class image_public(_init_image):
"""List public images"""
def __init__(self, arguments={}):
super(image_public, self).__init__(arguments)
self.arguments['detail'] = FlagArgument('show detailed output', '-l')
self.arguments['container_format'] =\
ValueArgument('filter by container format', '--container-format')
self.arguments['disk_format'] =\
ValueArgument('filter by disk format', '--disk-format')
self.arguments['name'] = ValueArgument('filter by name', '--name')
self.arguments['size_min'] =\
IntArgument('filter by minimum size', '--size-min')
self.arguments['size_max'] =\
IntArgument('filter by maximum size', '--size-max')
self.arguments['status'] =\
ValueArgument('filter by status', '--status')
self.arguments['order'] =\
ValueArgument('order by FIELD (use a - prefix to reverse order)',
'--order', default='')
arguments = dict(
detail=FlagArgument('show detailed output', '-l'),
container_format=ValueArgument(
'filter by container format',
'--container-format'),
disk_format=ValueArgument('filter by disk format', '--disk-format'),
name=ValueArgument('filter by name', '--name'),
size_min=IntArgument('filter by minimum size', '--size-min'),
size_max=IntArgument('filter by maximum size', '--size-max'),
status=ValueArgument('filter by status', '--status'),
order=ValueArgument(
'order by FIELD ( - to reverse order)',
'--order',
default=''),
limit=IntArgument('limit the number of images in list', '-n'),
more=FlagArgument(
'output results in pages (-n to set items per page, default 10)',
'--more')
)
def main(self):
super(self.__class__, self).main()
filters = {}
for arg in ('container_format',
'disk_format',
'name',
'size_min',
'size_max',
'status'):
val = self.get_argument(arg)
if val is not None:
filters[arg] = val
order = self.get_argument('order')
detail = self.get_argument('detail')
for arg in set(
[
'container_format',
'disk_format',
'name',
'size_min',
'size_max',
'status'
]).intersection(self.arguments):
filters[arg] = self[arg]
order = self['order']
detail = self['detail']
try:
images = self.client.list_public(detail, filters, order)
except ClientError as ce:
raise_if_connection_error(ce, base_url='image.url')
raiseCLIError(ce)
except Exception as err:
raiseCLIError(err)
print_items(images, title=('name',), with_enumeration=True)
if self['more']:
print_items(
images,
title=('name',),
with_enumeration=True,
page_size=self['limit'] if self['limit'] else 10)
elif self['limit']:
print_items(
images[:self['limit']],
title=('name',),
with_enumeration=True)
else:
print_items(images, title=('name',), with_enumeration=True)
@command(image_cmds)
class image_meta(_init_image):
"""Get image metadata"""
"""Get image metadata
Image metadata include:
- image file information (location, size, etc.)
- image information (id, name, etc.)
- image os properties (os, fs, etc.)
"""
def main(self, image_id):
super(self.__class__, self).main()
try:
image = self.client.get_meta(image_id)
except ClientError as err:
except ClientError as ce:
if ce.status == 404:
raiseCLIError(ce,
'No image with id %s found' % image_id,
details=about_image_id)
raise_if_connection_error(ce, base_url='image.url')
raiseCLIError(ce)
except Exception as err:
raiseCLIError(err)
print_dict(image)
@command(image_cmds)
class image_register(_init_image):
"""(Re)Register an image
call with --update to update image properties
"""
def __init__(self, arguments={}):
super(image_register, self).__init__(arguments)
self.arguments['checksum'] =\
ValueArgument('set image checksum', '--checksum')
self.arguments['container_format'] =\
ValueArgument('set container format', '--container-format')
self.arguments['disk_format'] =\
ValueArgument('set disk format', '--disk-format')
self.arguments['id'] = ValueArgument('set image ID', '--id')
self.arguments['owner'] =\
ValueArgument('set image owner (admin only)', '--owner')
self.arguments['properties'] =\
KeyValueArgument(parsed_name='--property',
help='add property in key=value form (can be repeated)')
self.arguments['is_public'] =\
FlagArgument('mark image as public', '--public')
self.arguments['size'] = IntArgument('set image size', '--size')
self.arguments['update'] = FlagArgument(
'update an existing image properties', '--update')
"""(Re)Register an image"""
arguments = dict(
checksum=ValueArgument('set image checksum', '--checksum'),
container_format=ValueArgument(
'set container format',
'--container-format'),
disk_format=ValueArgument('set disk format', '--disk-format'),
id=ValueArgument('set image ID', '--id'),
owner=ValueArgument('set image owner (admin only)', '--owner'),
properties=KeyValueArgument(
'add property in key=value form (can be repeated)',
'--property'),
is_public=FlagArgument('mark image as public', '--public'),
size=IntArgument('set image size', '--size'),
update=FlagArgument('update existing image properties', '--update')
)
def main(self, name, location):
super(self.__class__, self).main()
......@@ -159,24 +188,27 @@ class image_register(_init_image):
location = 'pithos://%s/%s/%s' % (account, container, location)
params = {}
for key in ('checksum',
'container_format',
'disk_format',
'id',
'owner',
'size',
'is_public'):
val = self.get_argument(key)
if val is not None:
params[key] = val
update = self.get_argument('update')
properties = self.get_argument('properties')
for key in set(
[
'checksum',
'container_format',
'disk_format',
'id',
'owner',
'size',
'is_public'
]).intersection(self.arguments):
params[key] = self[key]
try:
if update:
properties = self['properties']
if self['update']:
self.client.reregister(location, name, params, properties)
else:
self.client.register(name, location, params, properties)
except ClientError as ce:
raise_if_connection_error(ce, base_url='image.url')
raiseCLIError(ce)
except Exception as err:
raiseCLIError(err)
......@@ -189,24 +221,28 @@ class image_members(_init_image):
super(self.__class__, self).main()
try:
members = self.client.list_members(image_id)
except ClientError as ce:
raise_if_connection_error(ce, base_url='image.url')
raiseCLIError(ce)
except Exception as err:
raiseCLIError(err)
for member in members:
print(member['member_id'])
print_items(members)
@command(image_cmds)
class image_shared(_init_image):
"""List shared images"""
"""List images shared by a member"""
def main(self, member):
super(self.__class__, self).main()
try:
images = self.client.list_shared(member)
except ClientError as ce:
raise_if_connection_error(ce, base_url='image.url')
raiseCLIError(ce)
except Exception as err:
raiseCLIError(err)
for image in images:
print(image['image_id'])
print_items(images)
@command(image_cmds)
......@@ -217,6 +253,13 @@ class image_addmember(_init_image):
super(self.__class__, self).main()
try:
self.client.add_member(image_id, member)
except ClientError as ce:
if ce.status == 404:
raiseCLIError(ce,
'No image with id %s found' % image_id,
details=about_image_id)
raise_if_connection_error(ce, base_url='image.url')
raiseCLIError(ce)
except Exception as err:
raiseCLIError(err)
......@@ -229,6 +272,13 @@ class image_delmember(_init_image):
super(self.__class__, self).main()
try:
self.client.remove_member(image_id, member)
except ClientError as ce:
if ce.status == 404:
raiseCLIError(ce,
'No image with id %s found' % image_id,
details=about_image_id)
raise_if_connection_error(ce, base_url='image.url')
raiseCLIError(ce)
except Exception as err:
raiseCLIError(err)
......@@ -241,6 +291,13 @@ class image_setmembers(_init_image):
super(self.__class__, self).main()
try:
self.client.set_members(image_id, member)
except ClientError as ce: