diff --git a/novaclient/openstack/common/strutils.py b/novaclient/openstack/common/strutils.py new file mode 100644 index 000000000..7813b6422 --- /dev/null +++ b/novaclient/openstack/common/strutils.py @@ -0,0 +1,133 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +System-level utilities and helper functions. +""" + +import logging +import sys + +LOG = logging.getLogger(__name__) + + +def int_from_bool_as_string(subject): + """ + Interpret a string as a boolean and return either 1 or 0. + + Any string value in: + + ('True', 'true', 'On', 'on', '1') + + is interpreted as a boolean True. + + Useful for JSON-decoded stuff and config file parsing + """ + return bool_from_string(subject) and 1 or 0 + + +def bool_from_string(subject): + """ + Interpret a string as a boolean. + + Any string value in: + + ('True', 'true', 'On', 'on', 'Yes', 'yes', '1') + + is interpreted as a boolean True. + + Useful for JSON-decoded stuff and config file parsing + """ + if isinstance(subject, bool): + return subject + if isinstance(subject, basestring): + if subject.strip().lower() in ('true', 'on', 'yes', '1'): + return True + return False + + +def safe_decode(text, incoming=None, errors='strict'): + """ + Decodes incoming str using `incoming` if they're + not already unicode. + + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a unicode `incoming` encoded + representation of it. + :raises TypeError: If text is not an isntance of basestring + """ + if not isinstance(text, basestring): + raise TypeError("%s can't be decoded" % type(text)) + + if isinstance(text, unicode): + return text + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + try: + return text.decode(incoming, errors) + except UnicodeDecodeError: + # Note(flaper87) If we get here, it means that + # sys.stdin.encoding / sys.getdefaultencoding + # didn't return a suitable encoding to decode + # text. This happens mostly when global LANG + # var is not set correctly and there's no + # default encoding. In this case, most likely + # python will use ASCII or ANSI encoders as + # default encodings but they won't be capable + # of decoding non-ASCII characters. + # + # Also, UTF-8 is being used since it's an ASCII + # extension. + return text.decode('utf-8', errors) + + +def safe_encode(text, incoming=None, + encoding='utf-8', errors='strict'): + """ + Encodes incoming str/unicode using `encoding`. If + incoming is not specified, text is expected to + be encoded with current python's default encoding. + (`sys.getdefaultencoding`) + + :param incoming: Text's current encoding + :param encoding: Expected encoding for text (Default UTF-8) + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a bytestring `encoding` encoded + representation of it. + :raises TypeError: If text is not an isntance of basestring + """ + if not isinstance(text, basestring): + raise TypeError("%s can't be encoded" % type(text)) + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + if isinstance(text, unicode): + return text.encode(encoding, errors) + elif text and encoding != incoming: + # Decode text before encoding it with `encoding` + text = safe_decode(text, incoming, errors) + return text.encode(encoding, errors) + + return text diff --git a/novaclient/shell.py b/novaclient/shell.py index 2a2d09e4f..4ed2db357 100644 --- a/novaclient/shell.py +++ b/novaclient/shell.py @@ -49,6 +49,7 @@ import novaclient from novaclient import client from novaclient import exceptions as exc import novaclient.extension +from novaclient.openstack.common import strutils from novaclient import utils from novaclient.v1_1 import shell as shell_v1_1 @@ -738,11 +739,11 @@ class OpenStackHelpFormatter(argparse.HelpFormatter): def main(): try: - OpenStackComputeShell().main(sys.argv[1:]) + OpenStackComputeShell().main(map(strutils.safe_decode, sys.argv[1:])) except Exception as e: logger.debug(e, exc_info=1) - print("ERROR: %s" % unicode(e), file=sys.stderr) + print("ERROR: %s" % strutils.safe_encode(unicode(e)), file=sys.stderr) sys.exit(1) diff --git a/novaclient/utils.py b/novaclient/utils.py index 8c63452c7..49e97f80a 100644 --- a/novaclient/utils.py +++ b/novaclient/utils.py @@ -6,6 +6,7 @@ import uuid import prettytable from novaclient import exceptions +from novaclient.openstack.common import strutils def arg(*args, **kwargs): @@ -139,7 +140,7 @@ def pretty_choice_list(l): def print_list(objs, fields, formatters={}, sortby_index=0): - if sortby_index == None: + if sortby_index is None: sortby = None else: sortby = fields[sortby_index] @@ -162,9 +163,9 @@ def print_list(objs, fields, formatters={}, sortby_index=0): pt.add_row(row) if sortby is not None: - print(pt.get_string(sortby=sortby)) + print(strutils.safe_encode(pt.get_string(sortby=sortby))) else: - print(pt.get_string()) + print(strutils.safe_encode(pt.get_string())) def print_dict(d, dict_property="Property"): @@ -184,7 +185,7 @@ def print_dict(d, dict_property="Property"): col1 = '' else: pt.add_row([k, v]) - print(pt.get_string()) + print(strutils.safe_encode(pt.get_string())) def find_resource(manager, name_or_id): @@ -203,9 +204,9 @@ def find_resource(manager, name_or_id): # now try to get entity as uuid try: - uuid.UUID(str(name_or_id)) + uuid.UUID(strutils.safe_encode(name_or_id)) return manager.get(name_or_id) - except (ValueError, exceptions.NotFound): + except (TypeError, ValueError, exceptions.NotFound): pass # for str id which is not uuid (for Flavor search currently) diff --git a/novaclient/v1_1/base.py b/novaclient/v1_1/base.py index d2f384287..fce840b1b 100644 --- a/novaclient/v1_1/base.py +++ b/novaclient/v1_1/base.py @@ -18,6 +18,7 @@ import base64 from novaclient import base +from novaclient.openstack.common import strutils # FIXME(sirp): Now that v1_0 has been removed, this can be merged with @@ -72,8 +73,8 @@ class BootingManagerWithFind(base.ManagerWithFind): if userdata: if hasattr(userdata, 'read'): userdata = userdata.read() - elif isinstance(userdata, unicode): - userdata = userdata.encode('utf-8') + + userdata = strutils.safe_encode(userdata) body["server"]["user_data"] = base64.b64encode(userdata) if meta: body["server"]["metadata"] = meta diff --git a/openstack-common.conf b/openstack-common.conf index 76f08056e..833c69a16 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=setup,timeutils +modules=setup,timeutils,strutils # The base module to hold the copy of openstack.common base=novaclient