From d6ef83021e2d8ea4ff7efc5acf7e40f7227a8a26 Mon Sep 17 00:00:00 2001
From: Alistair Coles <alistair.coles@hpe.com>
Date: Wed, 3 Feb 2016 13:44:53 +0000
Subject: [PATCH] Support --os-identity-api-version option

Add support for the auth-version to be specified using
--os-identity-api-version or OS_IDENTITY_API_VERSION
for compatibility with other openstack client command
line options.

The auth version used will be selected as follows:

- if either --auth-version or --os-identity-api-version
  is set, use that value
- otherwise use the value of ST_AUTH_VERSION, if set
- otherwise use the value of OS_AUTH_VERSION, if set
- otherwise (new behaviour) use the value of
  OS_IDENTITY_API_VERSION, if set
- otherwise default to 1.0

Note that before this change the auth version might have
defaulted to 1.0 despite OS_IDENTITY_API_VERSION being set,
but with this change OS_IDENTITY_API_VERSION is preferred.

Change-Id: Ifba4c4e43560ede3013337b8cdbc77dc2de6e8ff
Closes-Bug: #1541273
---
 swiftclient/shell.py     |  23 ++++++---
 tests/unit/test_shell.py | 104 +++++++++++++++++++++++++++++++++++----
 2 files changed, 112 insertions(+), 15 deletions(-)

diff --git a/swiftclient/shell.py b/swiftclient/shell.py
index e427a893..4cad5741 100755
--- a/swiftclient/shell.py
+++ b/swiftclient/shell.py
@@ -1193,7 +1193,9 @@ def main(arguments=None):
                           usage='''
 usage: %prog [--version] [--help] [--os-help] [--snet] [--verbose]
              [--debug] [--info] [--quiet] [--auth <auth_url>]
-             [--auth-version <auth_version>] [--user <username>]
+             [--auth-version <auth_version> |
+                 --os-identity-api-version <auth_version> ]
+             [--user <username>]
              [--key <api_key>] [--retries <num_retries>]
              [--os-username <auth-user-name>] [--os-password <auth-password>]
              [--os-user-id <auth-user-id>]
@@ -1254,6 +1256,15 @@ Examples:
 
   %prog list --lh
 '''.strip('\n'))
+
+    default_auth_version = '1.0'
+    for k in ('ST_AUTH_VERSION', 'OS_AUTH_VERSION', 'OS_IDENTITY_API_VERSION'):
+        try:
+            default_auth_version = environ[k]
+            break
+        except KeyError:
+            pass
+
     parser.add_option('--os-help', action='store_true', dest='os_help',
                       help='Show OpenStack authentication options.')
     parser.add_option('--os_help', action='store_true', help=SUPPRESS_HELP)
@@ -1272,14 +1283,14 @@ Examples:
     parser.add_option('-A', '--auth', dest='auth',
                       default=environ.get('ST_AUTH'),
                       help='URL for obtaining an auth token.')
-    parser.add_option('-V', '--auth-version',
+    parser.add_option('-V', '--auth-version', '--os-identity-api-version',
                       dest='auth_version',
-                      default=environ.get('ST_AUTH_VERSION',
-                                          (environ.get('OS_AUTH_VERSION',
-                                                       '1.0'))),
+                      default=default_auth_version,
                       type=str,
                       help='Specify a version for authentication. '
-                           'Defaults to 1.0.')
+                           'Defaults to env[ST_AUTH_VERSION], '
+                           'env[OS_AUTH_VERSION], env[OS_IDENTITY_API_VERSION]'
+                           ' or 1.0.')
     parser.add_option('-U', '--user', dest='user',
                       default=environ.get('ST_USER'),
                       help='User name for obtaining an auth token.')
diff --git a/tests/unit/test_shell.py b/tests/unit/test_shell.py
index 00546f6b..6bb97c58 100644
--- a/tests/unit/test_shell.py
+++ b/tests/unit/test_shell.py
@@ -1291,22 +1291,25 @@ class TestParsing(TestBase):
             result[0], result[1] = swiftclient.shell.parse_args(parser, args)
         return fake_command
 
-    def _verify_opts(self, actual_opts, opts, os_opts={}, os_opts_dict={}):
+    def _verify_opts(self, actual_opts, expected_opts, expected_os_opts=None,
+                     expected_os_opts_dict=None):
         """
         Check parsed options are correct.
 
-        :param opts: v1 style options.
-        :param os_opts: openstack style options.
-        :param os_opts_dict: openstack options that should be found in the
-                             os_options dict.
+        :param expected_opts: v1 style options.
+        :param expected_os_opts: openstack style options.
+        :param expected_os_opts_dict: openstack options that should be found in
+                                      the os_options dict.
         """
+        expected_os_opts = expected_os_opts or {}
+        expected_os_opts_dict = expected_os_opts_dict or {}
         # check the expected opts are set
-        for key, v in opts.items():
+        for key, v in expected_opts.items():
             actual = getattr(actual_opts, key)
             self.assertEqual(v, actual, 'Expected %s for key %s, found %s' %
                              (v, key, actual))
 
-        for key, v in os_opts.items():
+        for key, v in expected_os_opts.items():
             actual = getattr(actual_opts, "os_" + key)
             self.assertEqual(v, actual, 'Expected %s for key %s, found %s' %
                              (v, key, actual))
@@ -1327,8 +1330,8 @@ class TestParsing(TestBase):
             if key == 'object_storage_url':
                 # exceptions to the pattern...
                 cli_key = 'storage_url'
-            if cli_key in os_opts_dict:
-                expect = os_opts_dict[cli_key]
+            if cli_key in expected_os_opts_dict:
+                expect = expected_os_opts_dict[cli_key]
             else:
                 expect = None
             actual = actual_os_opts_dict[key]
@@ -1386,6 +1389,89 @@ class TestParsing(TestBase):
             swiftclient.shell.main(args)
         self._verify_opts(result[0], opts, os_opts, os_opts_dict)
 
+    def test_os_identity_api_version(self):
+        os_opts = {"password": "secret",
+                   "username": "user",
+                   "auth_url": "http://example.com:5000/v3",
+                   "identity-api-version": "3"}
+
+        # check os_identity_api_version is sufficient in place of auth_version
+        args = _make_args("stat", {}, os_opts, '-')
+        result = [None, None]
+        fake_command = self._make_fake_command(result)
+        with mock.patch.dict(os.environ, {}):
+            with mock.patch('swiftclient.shell.st_stat', fake_command):
+                swiftclient.shell.main(args)
+        expected_opts = {'auth_version': '3'}
+        expected_os_opts = {"password": "secret",
+                            "username": "user",
+                            "auth_url": "http://example.com:5000/v3"}
+        self._verify_opts(result[0], expected_opts, expected_os_opts, {})
+
+        # check again using environment variables
+        args = _make_args("stat", {}, {})
+        env = _make_env({}, os_opts)
+        result = [None, None]
+        fake_command = self._make_fake_command(result)
+        with mock.patch.dict(os.environ, env):
+            with mock.patch('swiftclient.shell.st_stat', fake_command):
+                swiftclient.shell.main(args)
+        self._verify_opts(result[0], expected_opts, expected_os_opts, {})
+
+        # check that last of auth-version, os-identity-api-version is preferred
+        args = _make_args("stat", {}, os_opts, '-') + ['--auth-version', '2.0']
+        result = [None, None]
+        fake_command = self._make_fake_command(result)
+        with mock.patch.dict(os.environ, {}):
+            with mock.patch('swiftclient.shell.st_stat', fake_command):
+                swiftclient.shell.main(args)
+        expected_opts = {'auth_version': '2.0'}
+        self._verify_opts(result[0], expected_opts, expected_os_opts, {})
+
+        # now put auth_version ahead of os-identity-api-version
+        args = _make_args("stat", {"auth_version": "2.0"}, os_opts, '-')
+        result = [None, None]
+        fake_command = self._make_fake_command(result)
+        with mock.patch.dict(os.environ, {}):
+            with mock.patch('swiftclient.shell.st_stat', fake_command):
+                swiftclient.shell.main(args)
+        expected_opts = {'auth_version': '3'}
+        self._verify_opts(result[0], expected_opts, expected_os_opts, {})
+
+        # check that OS_AUTH_VERSION overrides OS_IDENTITY_API_VERSION
+        args = _make_args("stat", {}, {})
+        env = _make_env({}, os_opts)
+        env.update({'OS_AUTH_VERSION': '2.0'})
+        result = [None, None]
+        fake_command = self._make_fake_command(result)
+        with mock.patch.dict(os.environ, env):
+            with mock.patch('swiftclient.shell.st_stat', fake_command):
+                swiftclient.shell.main(args)
+        expected_opts = {'auth_version': '2.0'}
+        self._verify_opts(result[0], expected_opts, expected_os_opts, {})
+
+        # check that ST_AUTH_VERSION overrides OS_IDENTITY_API_VERSION
+        args = _make_args("stat", {}, {})
+        env = _make_env({}, os_opts)
+        env.update({'ST_AUTH_VERSION': '2.0'})
+        result = [None, None]
+        fake_command = self._make_fake_command(result)
+        with mock.patch.dict(os.environ, env):
+            with mock.patch('swiftclient.shell.st_stat', fake_command):
+                swiftclient.shell.main(args)
+        self._verify_opts(result[0], expected_opts, expected_os_opts, {})
+
+        # check that ST_AUTH_VERSION overrides OS_AUTH_VERSION
+        args = _make_args("stat", {}, {})
+        env = _make_env({}, os_opts)
+        env.update({'ST_AUTH_VERSION': '2.0', 'OS_AUTH_VERSION': '3'})
+        result = [None, None]
+        fake_command = self._make_fake_command(result)
+        with mock.patch.dict(os.environ, env):
+            with mock.patch('swiftclient.shell.st_stat', fake_command):
+                swiftclient.shell.main(args)
+        self._verify_opts(result[0], expected_opts, expected_os_opts, {})
+
     def test_args_v3(self):
         opts = {"auth_version": "3"}
         os_opts = {"password": "secret",