Handle formatting of subcommand name in error output

On Python 2, decoding all arguments leads to the possibility that users
that use the wrong command or mistype the name will see error output
with a unicode string's representation instead of one without it. To
avoid this we try and find the first non-option string in the argument
list and replace it with an string that is not text only on Python 2. If
we encoded the string at all times, then users installing glanceclient
on Python 3 would see b'invalid-subcommand' instead. That's as bad as
seeing u'invalid-subcommand' on Python 2.

Closes-bug: 1533090
Change-Id: I018769e159a607ebb233902cbeb13b95ca417190
This commit is contained in:
Ian Cordasco 2016-12-07 15:39:22 -06:00
parent 611401d229
commit 81039a1e36
2 changed files with 64 additions and 1 deletions
glanceclient

@ -31,6 +31,7 @@ import traceback
from oslo_utils import encodeutils
from oslo_utils import importutils
import six
import six.moves.urllib.parse as urlparse
import glanceclient
@ -539,7 +540,13 @@ class OpenStackImagesShell(object):
self.do_help(options, parser=parser)
return 0
# Short-circuit and deal with help command right away.
# NOTE(sigmavirus24): Above, args is defined as the left over
# arguments from parser.parse_known_args(). This allows us to
# skip any parameters to command-line flags that may have been passed
# to glanceclient, e.g., --os-auth-token.
self._fixup_subcommand(args, argv)
# short-circuit and deal with help command right away.
sub_parser = _get_subparser(api_version)
args = sub_parser.parse_args(argv)
@ -609,6 +616,33 @@ class OpenStackImagesShell(object):
print("To display trace use next command:\n"
"osprofiler trace show --html %s " % trace_id)
@staticmethod
def _fixup_subcommand(unknown_args, argv):
# NOTE(sigmavirus24): Sometimes users pass the wrong subcommand name
# to glanceclient. If they're using Python 2 they will see an error:
# > invalid choice: u'imgae-list' (choose from ...)
# To avoid this, we look at the extra args already parsed from above
# and try to predict what the subcommand will be based on it being the
# first non - or -- prefixed argument in args. We then find that in
# argv and encode it from unicode so users don't see the pesky `u'`
# prefix.
for arg in unknown_args:
if not arg.startswith('-'): # This will cover both - and --
subcommand_name = arg
break
else:
subcommand_name = ''
if (subcommand_name and six.PY2 and
isinstance(subcommand_name, six.text_type)):
# NOTE(sigmavirus24): if we found a subcommand name, then let's
# find it in the argv list and replace it with a bytes object
# instead. Note, that if we encode the argument on Python 3, the
# user will instead see a pesky `b'` string instead of the `u'`
# string we mention above.
subcommand_index = argv.index(subcommand_name)
argv[subcommand_index] = encodeutils.safe_encode(subcommand_name)
@utils.arg('command', metavar='<subcommand>', nargs='?',
help='Display help for <subcommand>.')
def do_help(self, args, parser):

@ -163,6 +163,35 @@ class ShellTest(testutils.TestCase):
sys.stderr = orig_stderr
return (stdout, stderr)
def test_fixup_subcommand(self):
arglist = [u'image-list', u'--help']
if six.PY2:
expected_arglist = [b'image-list', u'--help']
elif six.PY3:
expected_arglist = [u'image-list', u'--help']
openstack_shell.OpenStackImagesShell._fixup_subcommand(
arglist, arglist
)
self.assertEqual(expected_arglist, arglist)
def test_fixup_subcommand_with_options_preceding(self):
arglist = [u'--os-auth-token', u'abcdef', u'image-list', u'--help']
unknown = arglist[2:]
if six.PY2:
expected_arglist = [
u'--os-auth-token', u'abcdef', b'image-list', u'--help'
]
elif six.PY3:
expected_arglist = [
u'--os-auth-token', u'abcdef', u'image-list', u'--help'
]
openstack_shell.OpenStackImagesShell._fixup_subcommand(
unknown, arglist
)
self.assertEqual(expected_arglist, arglist)
def test_help_unknown_command(self):
shell = openstack_shell.OpenStackImagesShell()
argstr = '--os-image-api-version 2 help foofoo'