Commit 79bedf0d authored by Ilias Tsitsimpis's avatar Ilias Tsitsimpis
Browse files

Merge pull request #8 from saxtouri/feature-encodings

Handle input and output encodings in console as well as HTTP response and request headers.
Console input/output is encoded/decoded from/to preferred encoding.
parents c86c360d 8a4da78c
...@@ -38,7 +38,7 @@ from inspect import getargspec ...@@ -38,7 +38,7 @@ from inspect import getargspec
from kamaki.cli.argument import ArgumentParseManager from kamaki.cli.argument import ArgumentParseManager
from kamaki.cli.history import History from kamaki.cli.history import History
from kamaki.cli.utils import print_dict, red, magenta, yellow from kamaki.cli.utils import print_dict, red, magenta, yellow, pref_enc
from kamaki.cli.errors import CLIError, CLICmdSpecError from kamaki.cli.errors import CLIError, CLICmdSpecError
from kamaki.cli import logger from kamaki.cli import logger
from kamaki.clients.astakos import CachedAstakosClient from kamaki.clients.astakos import CachedAstakosClient
...@@ -432,7 +432,7 @@ def update_parser_help(parser, cmd): ...@@ -432,7 +432,7 @@ def update_parser_help(parser, cmd):
def print_error_message(cli_err, out=stderr): def print_error_message(cli_err, out=stderr):
errmsg = '%s' % cli_err errmsg = u'%s' % cli_err
if cli_err.importance == 1: if cli_err.importance == 1:
errmsg = magenta(errmsg) errmsg = magenta(errmsg)
elif cli_err.importance == 2: elif cli_err.importance == 2:
...@@ -441,7 +441,7 @@ def print_error_message(cli_err, out=stderr): ...@@ -441,7 +441,7 @@ def print_error_message(cli_err, out=stderr):
errmsg = red(errmsg) errmsg = red(errmsg)
out.write(errmsg) out.write(errmsg)
for errmsg in cli_err.details: for errmsg in cli_err.details:
out.write('| %s\n' % errmsg) out.write(u'| %s\n' % errmsg)
out.flush() out.flush()
...@@ -500,6 +500,10 @@ def main(func): ...@@ -500,6 +500,10 @@ def main(func):
def wrap(): def wrap():
try: try:
exe = basename(argv[0]) exe = basename(argv[0])
internal_argv = [arg.decode(pref_enc) for arg in argv]
for arg in reversed(internal_argv):
argv.insert(0, arg)
argv.pop()
parser = ArgumentParseManager(exe) parser = ArgumentParseManager(exe)
if parser.arguments['version'].value: if parser.arguments['version'].value:
...@@ -511,7 +515,8 @@ def main(func): ...@@ -511,7 +515,8 @@ def main(func):
logger.set_log_filename(log_file) logger.set_log_filename(log_file)
global filelog global filelog
filelog = logger.add_file_logger(__name__.split('.')[0]) filelog = logger.add_file_logger(__name__.split('.')[0])
filelog.info('* Initial Call *\n%s\n- - -' % ' '.join(argv))
filelog.info('%s\n- - -' % ' '.join(argv))
from kamaki.cli.utils import suggest_missing from kamaki.cli.utils import suggest_missing
global _colors global _colors
......
...@@ -310,7 +310,7 @@ class DataSizeArgument(ValueArgument): ...@@ -310,7 +310,7 @@ class DataSizeArgument(ValueArgument):
limit = int(user_input) limit = int(user_input)
except ValueError: except ValueError:
index = 0 index = 0
digits = [str(num) for num in range(0, 10)] + ['.'] digits = ['%s' % num for num in range(0, 10)] + ['.']
while user_input[index] in digits: while user_input[index] in digits:
index += 1 index += 1
limit = user_input[:index] limit = user_input[:index]
......
...@@ -33,11 +33,11 @@ ...@@ -33,11 +33,11 @@
from cmd import Cmd from cmd import Cmd
from os import popen from os import popen
from sys import stdout, stderr from sys import stdout
from kamaki.cli import exec_cmd, print_error_message, print_subcommands_help from kamaki.cli import exec_cmd, print_error_message, print_subcommands_help
from kamaki.cli.argument import ArgumentParseManager from kamaki.cli.argument import ArgumentParseManager
from kamaki.cli.utils import print_dict, split_input from kamaki.cli.utils import print_dict, split_input, pref_enc
from kamaki.cli.history import History from kamaki.cli.history import History
from kamaki.cli.errors import CLIError from kamaki.cli.errors import CLIError
from kamaki.clients import ClientError from kamaki.clients import ClientError
...@@ -190,6 +190,7 @@ class Shell(Cmd): ...@@ -190,6 +190,7 @@ class Shell(Cmd):
<cmd> <term> <term> <args> is always parsed to most specific <cmd> <term> <term> <args> is always parsed to most specific
even if cmd_term_term is not a terminal path even if cmd_term_term is not a terminal path
""" """
line = line.decode(pref_enc)
subcmd, cmd_args = cmd.parse_out(split_input(line)) subcmd, cmd_args = cmd.parse_out(split_input(line))
self._history.add(' '.join([cmd.path.replace('_', ' '), line])) self._history.add(' '.join([cmd.path.replace('_', ' '), line]))
cmd_parser = ArgumentParseManager( cmd_parser = ArgumentParseManager(
......
...@@ -33,8 +33,8 @@ ...@@ -33,8 +33,8 @@
from kamaki.cli.logger import get_logger from kamaki.cli.logger import get_logger
from kamaki.cli.utils import ( from kamaki.cli.utils import (
print_list, print_dict, print_json, print_items, ask_user, print_list, print_dict, print_json, print_items, ask_user, pref_enc,
filter_dicts_by_dict, DontRaiseUnicodeError, pref_enc) filter_dicts_by_dict)
from kamaki.cli.argument import FlagArgument, ValueArgument from kamaki.cli.argument import FlagArgument, ValueArgument
from kamaki.cli.errors import CLIInvalidArgument, CLIBaseUrlError from kamaki.cli.errors import CLIInvalidArgument, CLIBaseUrlError
from sys import stdin, stdout, stderr from sys import stdin, stdout, stderr
...@@ -88,9 +88,6 @@ class _command_init(object): ...@@ -88,9 +88,6 @@ class _command_init(object):
_in=None, _out=None, _err=None): _in=None, _out=None, _err=None):
self._in, self._out, self._err = ( self._in, self._out, self._err = (
_in or stdin, _out or stdout, _err or stderr) _in or stdin, _out or stdout, _err or stderr)
self._in = codecs.getreader(pref_enc)(_in or stdin)
self._out = codecs.getwriter(pref_enc)(_out or stdout)
self._err = codecs.getwriter(pref_enc)(_err or stderr)
self.required = getattr(self, 'required', None) self.required = getattr(self, 'required', None)
if hasattr(self, 'arguments'): if hasattr(self, 'arguments'):
arguments.update(self.arguments) arguments.update(self.arguments)
...@@ -128,18 +125,15 @@ class _command_init(object): ...@@ -128,18 +125,15 @@ class _command_init(object):
raise CLIBaseUrlError(service=service) raise CLIBaseUrlError(service=service)
return cls(URL, TOKEN) return cls(URL, TOKEN)
@DontRaiseUnicodeError
def write(self, s): def write(self, s):
self._out.write(s) self._out.write(s.encode(pref_enc, errors='replace'))
self._out.flush() self._out.flush()
@DontRaiseUnicodeError
def writeln(self, s=''): def writeln(self, s=''):
self.write('%s\n' % s) self.write('%s\n' % s)
@DontRaiseUnicodeError
def error(self, s=''): def error(self, s=''):
self._err.write('%s\n' % s) self._err.write(('%s\n' % s).encode(pref_enc, errors='replace'))
self._err.flush() self._err.flush()
def print_list(self, *args, **kwargs): def print_list(self, *args, **kwargs):
......
...@@ -35,7 +35,7 @@ from traceback import print_stack, print_exc ...@@ -35,7 +35,7 @@ from traceback import print_stack, print_exc
from astakosclient import AstakosClientException from astakosclient import AstakosClientException
from kamaki.clients import ClientError from kamaki.clients import ClientError
from kamaki.cli.errors import CLIError, raiseCLIError, CLISyntaxError from kamaki.cli.errors import CLIError, CLISyntaxError
from kamaki.cli import _debug, kloger from kamaki.cli import _debug, kloger
from kamaki.cli.utils import format_size from kamaki.cli.utils import format_size
...@@ -53,9 +53,18 @@ class generic(object): ...@@ -53,9 +53,18 @@ class generic(object):
if _debug: if _debug:
print_stack() print_stack()
print_exc(e) print_exc(e)
if isinstance(e, CLIError) or isinstance(e, ClientError): if isinstance(e, CLIError):
raiseCLIError(e) raise e
raiseCLIError(e, details=['%s, -d for debug info' % type(e)]) elif isinstance(e, ClientError):
raise CLIError(
u'(%s) %s' % (getattr(e, 'status', 'no status'), e),
details=getattr(e, 'details', []),
importance=1 if (
e.status < 200) else 2 if (
e.status < 300) else 3 if (
e.status < 400) else 4)
raise CLIError(
'%s' % e, details=['%s, -d for debug info' % type(e)])
return _raise return _raise
@classmethod @classmethod
...@@ -66,7 +75,7 @@ class generic(object): ...@@ -66,7 +75,7 @@ class generic(object):
except ClientError as ce: except ClientError as ce:
ce_msg = ('%s' % ce).lower() ce_msg = ('%s' % ce).lower()
if ce.status == 401: if ce.status == 401:
raiseCLIError(ce, 'Authorization failed', details=[ raise CLIError('Authorization failed', details=[
'Make sure a valid token is provided:', 'Make sure a valid token is provided:',
' # to check if token is valid', ' # to check if token is valid',
' $ kamaki user authenticate', ' $ kamaki user authenticate',
...@@ -74,17 +83,17 @@ class generic(object): ...@@ -74,17 +83,17 @@ class generic(object):
' $ kamaki config set cloud.default.token <token>', ' $ kamaki config set cloud.default.token <token>',
' # to get current token:', ' # to get current token:',
' $ kamaki config get cloud.default.token', ' $ kamaki config get cloud.default.token',
'%s' % ce,
] + CLOUDNAME) ] + CLOUDNAME)
elif ce.status in range(-12, 200) + [302, 401, 500]: elif ce.status in range(-12, 200) + [302, 401, 500]:
raiseCLIError(ce, importance=3, details=[ raise CLIError('%s' % ce, importance=3)
'Check if service is up'])
elif ce.status == 404 and 'kamakihttpresponse' in ce_msg: elif ce.status == 404 and 'kamakihttpresponse' in ce_msg:
client = getattr(self, 'client', None) client = getattr(self, 'client', None)
if not client: if not client:
raise raise
url = getattr(client, 'base_url', '<empty>') url = getattr(client, 'base_url', '<empty>')
msg = 'Invalid service URL %s' % url raise CLIError('Invalid service URL %s' % url, details=[
raiseCLIError(ce, msg, details=[ '%s' % ce,
'Check if authentication URL is correct', 'Check if authentication URL is correct',
' # check current URL', ' # check current URL',
' $ kamaki config get cloud.default.url', ' $ kamaki config get cloud.default.url',
...@@ -108,7 +117,8 @@ class user(object): ...@@ -108,7 +117,8 @@ class user(object):
try: try:
r = func(self, *args, **kwargs) r = func(self, *args, **kwargs)
except AstakosClientException as ace: except AstakosClientException as ace:
raiseCLIError(ace, 'Error in synnefo-AstakosClient') raise CLIError(
'Error in AstakosClient', details=['%s' % ace, ])
return r return r
return _raise return _raise
...@@ -119,7 +129,8 @@ class user(object): ...@@ -119,7 +129,8 @@ class user(object):
try: try:
client = getattr(self, 'client') client = getattr(self, 'client')
except AttributeError as ae: except AttributeError as ae:
raiseCLIError(ae, 'Client setup failure', importance=3) raise CLIError('Client setup failure', importance=3, details=[
'%s' % ae])
if not getattr(client, 'token', False): if not getattr(client, 'token', False):
kloger.warning( kloger.warning(
'No permanent token (try:' 'No permanent token (try:'
...@@ -146,7 +157,7 @@ class user(object): ...@@ -146,7 +157,7 @@ class user(object):
msg = ('Authorization failed for token %s' % token) if ( msg = ('Authorization failed for token %s' % token) if (
token) else 'No token provided', token) else 'No token provided',
details = [] if token else this._token_details details = [] if token else this._token_details
raiseCLIError(ce, msg, details=details) raise CLIError(msg, details=details + ['%s' % ce, ])
raise ce raise ce
self._raise = func self._raise = func
return _raise return _raise
...@@ -221,11 +232,12 @@ class cyclades(object): ...@@ -221,11 +232,12 @@ class cyclades(object):
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except ValueError as ve: except ValueError as ve:
msg = 'Invalid cluster size value %s' % size msg = 'Invalid cluster size value %s' % size
raiseCLIError(ve, msg, importance=1, details=[ raise CLIError(msg, importance=1, details=[
'Cluster size must be a positive integer']) 'Cluster size must be a positive integer', '%s' % ve])
except AssertionError as ae: except AssertionError as ae:
raiseCLIError( raise CLIError(
ae, 'Invalid cluster size %s' % size, importance=1) 'Invalid cluster size %s' % size, importance=1, details=[
'%s' % ae])
except ClientError: except ClientError:
raise raise
return _raise return _raise
...@@ -238,15 +250,18 @@ class cyclades(object): ...@@ -238,15 +250,18 @@ class cyclades(object):
network_id = int(network_id) network_id = int(network_id)
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except ValueError as ve: except ValueError as ve:
msg = 'Invalid network id %s ' % network_id raise CLIError(
details = 'network id must be a positive integer' 'Invalid network id %s ' % network_id,
raiseCLIError(ve, msg, details=details, importance=1) details=[
'network id must be a positive integer', '%s' % ve],
importance=1)
except ClientError as ce: except ClientError as ce:
if network_id and ce.status == 404 and ( if network_id and ce.status == 404 and (
'network' in ('%s' % ce).lower() 'network' in ('%s' % ce).lower()
): ):
msg = 'No network with id %s found' % network_id, raise CLIError(
raiseCLIError(ce, msg, details=this.about_network_id) 'No network with id %s found' % network_id,
details=this.about_network_id + ['%s' % ce])
raise raise
return _raise return _raise
...@@ -268,15 +283,18 @@ class cyclades(object): ...@@ -268,15 +283,18 @@ class cyclades(object):
flavor_id = int(flavor_id) flavor_id = int(flavor_id)
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except ValueError as ve: except ValueError as ve:
msg = 'Invalid flavor id %s ' % flavor_id, raise CLIError(
details = 'Flavor id must be a positive integer' 'Invalid flavor id %s ' % flavor_id,
raiseCLIError(ve, msg, details=details, importance=1) details=[
'Flavor id must be a positive integer', '%s' % ve],
importance=1)
except ClientError as ce: except ClientError as ce:
if flavor_id and ce.status == 404 and ( if flavor_id and ce.status == 404 and (
'flavor' in ('%s' % ce).lower() 'flavor' in ('%s' % ce).lower()
): ):
msg = 'No flavor with id %s found' % flavor_id, raise CLIError(
raiseCLIError(ce, msg, details=this.about_flavor_id) 'No flavor with id %s found' % flavor_id,
details=this.about_flavor_id + ['%s' % ce, ])
raise raise
return _raise return _raise
...@@ -288,9 +306,11 @@ class cyclades(object): ...@@ -288,9 +306,11 @@ class cyclades(object):
server_id = int(server_id) server_id = int(server_id)
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except ValueError as ve: except ValueError as ve:
msg = 'Invalid virtual server id %s' % server_id, raise CLIError(
details = 'Server id must be a positive integer' 'Invalid virtual server id %s' % server_id,
raiseCLIError(ve, msg, details=details, importance=1) details=[
'Server id must be a positive integer', '%s' % ve],
importance=1)
except ClientError as ce: except ClientError as ce:
err_msg = ('%s' % ce).lower() err_msg = ('%s' % ce).lower()
if ( if (
...@@ -298,12 +318,14 @@ class cyclades(object): ...@@ -298,12 +318,14 @@ class cyclades(object):
) or ( ) or (
ce.status == 400 and 'not found' in err_msg ce.status == 400 and 'not found' in err_msg
): ):
msg = 'virtual server with id %s not found' % server_id, raise CLIError(
raiseCLIError(ce, msg, details=[ 'virtual server with id %s not found' % server_id,
details=[
'# to get ids of all servers', '# to get ids of all servers',
'$ kamaki server list', '$ kamaki server list',
'# to get server details', '# to get server details',
'$ kamaki server info <server id>']) '$ kamaki server info <server id>',
'%s' % ce])
raise raise
return _raise return _raise
...@@ -317,12 +339,14 @@ class cyclades(object): ...@@ -317,12 +339,14 @@ class cyclades(object):
if ce.status == 400 and profile and ( if ce.status == 400 and profile and (
'firewall' in ('%s' % ce).lower() 'firewall' in ('%s' % ce).lower()
): ):
msg = '%s is an invalid firewall profile term' % profile raise CLIError(
raiseCLIError(ce, msg, details=[ '%s is an invalid firewall profile term' % profile,
details=[
'Try one of the following:', 'Try one of the following:',
'* DISABLED: Shutdown firewall', '* DISABLED: Shutdown firewall',
'* ENABLED: Firewall in normal mode', '* ENABLED: Firewall in normal mode',
'* PROTECTED: Firewall in secure mode']) '* PROTECTED: Firewall in secure mode',
'%s' % ce])
raise raise
return _raise return _raise
...@@ -340,12 +364,13 @@ class cyclades(object): ...@@ -340,12 +364,13 @@ class cyclades(object):
err_msg = 'No nic %s on virtual server with id %s' % ( err_msg = 'No nic %s on virtual server with id %s' % (
nic_id, nic_id,
server_id) server_id)
raiseCLIError(ce, err_msg, details=[ raise CLIError(err_msg, details=[
'* check v. server with id %s: /server info %s' % ( '* check v. server with id %s: /server info %s' % (
server_id, server_id,
server_id), server_id),
'* list nics for v. server with id %s:' % server_id, '* list nics for v. server with id %s:' % server_id,
' /server addr %s' % server_id]) ' /server addr %s' % server_id,
'%s' % ce])
raise raise
return _raise return _raise
...@@ -359,8 +384,9 @@ class cyclades(object): ...@@ -359,8 +384,9 @@ class cyclades(object):
if key and ce.status == 404 and ( if key and ce.status == 404 and (
'metadata' in ('%s' % ce).lower() 'metadata' in ('%s' % ce).lower()
): ):
raiseCLIError( raise CLIError(
ce, 'No virtual server metadata with key %s' % key) 'No virtual server metadata with key %s' % key,
details=['%s' % ce, ])
raise raise
return _raise return _raise
...@@ -389,8 +415,9 @@ class plankton(object): ...@@ -389,8 +415,9 @@ class plankton(object):
except ClientError as ce: except ClientError as ce:
if image_id or (ce.status in (404, 400) and ( if image_id or (ce.status in (404, 400) and (
'image not found' in ('%s' % ce).lower())): 'image not found' in ('%s' % ce).lower())):
msg = 'No image with id %s found' % image_id raise CLIError(
raiseCLIError(ce, msg, details=this.about_image_id) 'No image with id %s found' % image_id,
details=this.about_image_id + ['%s' % ce])
raise raise
return _raise return _raise
...@@ -404,8 +431,9 @@ class plankton(object): ...@@ -404,8 +431,9 @@ class plankton(object):
ce_msg = ('%s' % ce).lower() ce_msg = ('%s' % ce).lower()
if ce.status == 404 or ( if ce.status == 404 or (
ce.status == 400 and 'metadata' in ce_msg): ce.status == 400 and 'metadata' in ce_msg):
msg = 'No properties with key %s in this image' % key raise CLIError(
raiseCLIError(ce, msg) 'No properties with key %s in this image' % key,
details=['%s' % ce, ])
raise raise
return _raise return _raise
...@@ -434,10 +462,9 @@ class pithos(object): ...@@ -434,10 +462,9 @@ class pithos(object):
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except ClientError as ce: except ClientError as ce:
if ce.status == 403: if ce.status == 403:
raiseCLIError( raise CLIError(
ce,
'Invalid account credentials for this operation', 'Invalid account credentials for this operation',
details=['Check user account settings']) details=['Check user account settings', '%s' % ce])
raise raise
return _raise return _raise
...@@ -448,13 +475,14 @@ class pithos(object): ...@@ -448,13 +475,14 @@ class pithos(object):
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except ClientError as ce: except ClientError as ce:
if ce.status == 413: if ce.status == 413:
raiseCLIError(ce, 'User quota exceeded', details=[ raise CLIError('User quota exceeded', details=[
'* get quotas:', '* get quotas:',
' * upper total limit: /file quota', ' * upper total limit: /file quota',
' * container limit:', ' * container limit:',
' /file containerlimit get <container>', ' /file containerlimit get <container>',
'* set a higher container limit:', '* set a higher container limit:',
' /file containerlimit set <limit> <container>']) ' /file containerlimit set <limit> <container>',
'%s' % ce])
raise raise
return _raise return _raise
...@@ -469,8 +497,9 @@ class pithos(object): ...@@ -469,8 +497,9 @@ class pithos(object):
cont = ('%s or %s' % ( cont = ('%s or %s' % (
self.container, self.container,
dst_cont)) if dst_cont else self.container dst_cont)) if dst_cont else self.container
msg = 'Container "%s" does not exist' % cont, raise CLIError(
raiseCLIError(ce, msg, details=this.container_howto) 'Container "%s" does not exist' % cont,
details=this.container_howto + ['%s' % ce])
raise raise
return _raise return _raise
...@@ -480,14 +509,15 @@ class pithos(object): ...@@ -480,14 +509,15 @@ class pithos(object):
try: try:
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except IOError as ioe: except IOError as ioe:
msg = 'Failed to access a local file', raise CLIError(
raiseCLIError(ioe, msg, importance=2, details=[ 'Failed to access a local file', importance=2, details=[
'Check if the file exists. Also check if the remote', 'Check if the file exists. Also check if the remote',
'directories exist. All directories in a remote path', 'directories exist. All directories in a remote path',
'must exist to succesfully download a container or a', 'must exist to succesfully download a container or a',
'directory.', 'directory.',