From 5521e4c504c6a3a06f17a9e4f80444743aa293c7 Mon Sep 17 00:00:00 2001
From: Roxana Gherle <roxana.gherle@hp.com>
Date: Fri, 22 May 2015 16:22:35 -0700
Subject: [PATCH] Add --os-endpoint-type cli optional argument

User should be able to specify the endpoint type through
a CLI optional argument/ENV variable setting. We will name this new
optional argument: --os-endpoint-type (Env: OS_ENDPOINT_TYPE) and
based on the value given, the service API will use that specific
endpoint type. Possible values: public, admin, internal.

DocImpact
Closes-Bug: #1454392
Change-Id: Ife3d4e46b44c0ddcd712b1130e27e362545a9a29
---
 doc/source/configuration.rst                       |  8 +++++++-
 doc/source/man/openstack.rst                       |  5 +++++
 openstackclient/common/clientmanager.py            | 10 ++++++++--
 openstackclient/common/utils.py                    |  8 ++++++++
 openstackclient/compute/client.py                  |  5 +++++
 openstackclient/identity/client.py                 |  7 ++++++-
 openstackclient/image/client.py                    |  2 ++
 openstackclient/network/client.py                  |  6 ++++++
 openstackclient/object/client.py                   |  1 +
 openstackclient/shell.py                           | 14 +++++++++++++-
 openstackclient/tests/common/test_clientmanager.py | 11 +++++++++++
 openstackclient/tests/common/test_utils.py         | 10 ++++++++++
 openstackclient/tests/fakes.py                     |  2 ++
 openstackclient/tests/test_shell.py                |  8 ++++++++
 openstackclient/volume/client.py                   |  5 +++++
 15 files changed, 97 insertions(+), 5 deletions(-)

diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index e371e50448..b603eee425 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -78,6 +78,7 @@ The keys match the :program:`openstack` global options but without the
           username: openstack
           password: xyzpdq!lazydog
         region_name: DFW,ORD,IAD
+        endpoint_type: internal
 
 In the above example, the ``auth_url`` for the ``rackspace`` cloud is taken
 from :file:`clouds-public.yaml` (see below).
@@ -96,6 +97,7 @@ to the following options if the ``rackspace`` entry in :file:`clouds-public.yaml
     --os-username openstack
     --os-password xyzpdq!lazydog
     --os-region-name DFW
+    --os-endpoint-type internal
 
 and can be selected on the command line::
 
@@ -105,13 +107,17 @@ Note that multiple regions are listed in the ``rackspace`` entry.  An otherwise
 identical configuration is created for each region.  If ``-os-region-name`` is not
 specified on the command line, the first region in the list is used by default.
 
+The selection of ``endpoint_type`` (as seen above in the ``rackspace`` entry)
+is optional.  For this configuration to work, every service for this cloud
+instance must already be configured to support this type of endpoint.
+
 clouds-public.yaml
 ~~~~~~~~~~~~~~~~~~
 
 :file:`clouds-public.yaml` is a configuration file that is intended to contain
 public information about clouds that are common across a large number of users.
 The idea is that :file:`clouds-public.yaml` could easily be shared among users
-to simplify public could configuration.
+to simplify public cloud configuration.
 
 Similar to :file:`clouds.yaml`, OpenStackClient looks for
 :file:`clouds-public.yaml` in the following locations:
diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst
index 6d6dce4479..9d7115275c 100644
--- a/doc/source/man/openstack.rst
+++ b/doc/source/man/openstack.rst
@@ -120,6 +120,8 @@ OPTIONS
 :option:`--os-XXXX-api-version` <XXXX-api-version>
     Additional API version options will be available depending on the installed API libraries.
 
+:option:`--os-endpoint-type` <endpoint-type>
+    Endpoint type. Valid options are `public`, `admin` and `internal`.
 
 COMMANDS
 ========
@@ -344,6 +346,9 @@ The following environment variables can be set to alter the behaviour of :progra
 :envvar:`OS_XXXX_API_VERSION`
     Additional API version options will be available depending on the installed API libraries.
 
+:envvar:`OS_ENDPOINT_TYPE`
+    Endpoint type. Valid options are `public`, `admin` and `internal`.
+
 
 BUGS
 ====
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
index 6311c71a50..2a57b8ff72 100644
--- a/openstackclient/common/clientmanager.py
+++ b/openstackclient/common/clientmanager.py
@@ -86,6 +86,7 @@ class ClientManager(object):
         self._pw_callback = pw_func
         self._url = self._cli_options.auth.get('url', None)
         self._region_name = self._cli_options.region_name
+        self._endpoint_type = self._cli_options.endpoint_type
 
         self.timing = self._cli_options.timing
 
@@ -181,18 +182,23 @@ class ClientManager(object):
             self._auth_ref = self.auth.get_auth_ref(self.session)
         return self._auth_ref
 
-    def get_endpoint_for_service_type(self, service_type, region_name=None):
+    def get_endpoint_for_service_type(self, service_type, region_name=None,
+                                      endpoint_type='public'):
         """Return the endpoint URL for the service type."""
+        if not endpoint_type:
+            endpoint_type = 'public'
         # See if we are using password flow auth, i.e. we have a
         # service catalog to select endpoints from
         if self.auth_ref:
             endpoint = self.auth_ref.service_catalog.url_for(
                 service_type=service_type,
                 region_name=region_name,
+                endpoint_type=endpoint_type,
             )
         else:
             # Get the passed endpoint directly from the auth plugin
-            endpoint = self.auth.get_endpoint(self.session)
+            endpoint = self.auth.get_endpoint(self.session,
+                                              interface=endpoint_type)
         return endpoint
 
 
diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py
index aad0519c1b..caafa83732 100644
--- a/openstackclient/common/utils.py
+++ b/openstackclient/common/utils.py
@@ -368,3 +368,11 @@ def read_blob_file_contents(blob_file):
     except IOError:
         msg = "Error occurred trying to read from file %s"
         raise exceptions.CommandError(msg % blob_file)
+
+
+def build_kwargs_dict(arg_name, value):
+    """Return a dictionary containing `arg_name` if `value` is set."""
+    kwargs = {}
+    if value:
+        kwargs[arg_name] = value
+    return kwargs
diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py
index 93a7b71537..27d63a9575 100644
--- a/openstackclient/compute/client.py
+++ b/openstackclient/compute/client.py
@@ -48,12 +48,17 @@ def make_client(instance):
 
     extensions = [extension.Extension('list_extensions', list_extensions)]
 
+    # Remember endpoint_type only if it is set
+    kwargs = utils.build_kwargs_dict('endpoint_type',
+                                     instance._endpoint_type)
+
     client = compute_client(
         session=instance.session,
         extensions=extensions,
         http_log_debug=http_log_debug,
         timings=instance.timing,
         region_name=instance._region_name,
+        **kwargs
     )
 
     return client
diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py
index 4127a451e5..cc803511d6 100644
--- a/openstackclient/identity/client.py
+++ b/openstackclient/identity/client.py
@@ -46,10 +46,15 @@ def make_client(instance):
         API_VERSIONS)
     LOG.debug('Instantiating identity client: %s', identity_client)
 
+    # Remember interface only if endpoint_type is set
+    kwargs = utils.build_kwargs_dict('interface',
+                                     instance._endpoint_type)
+
     client = identity_client(
         session=instance.session,
         region_name=instance._region_name,
-    )
+        **kwargs
+        )
 
     return client
 
diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py
index c78f442524..8e2d6cd979 100644
--- a/openstackclient/image/client.py
+++ b/openstackclient/image/client.py
@@ -46,6 +46,7 @@ def make_client(instance):
     endpoint = instance.get_endpoint_for_service_type(
         API_NAME,
         region_name=instance._region_name,
+        endpoint_type=instance._endpoint_type,
     )
 
     client = image_client(
@@ -68,6 +69,7 @@ def make_client(instance):
         endpoint=instance.get_endpoint_for_service_type(
             IMAGE_API_TYPE,
             region_name=instance._region_name,
+            endpoint_type=instance._endpoint_type,
         )
     )
 
diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py
index 870566aaa4..de08e5e2fe 100644
--- a/openstackclient/network/client.py
+++ b/openstackclient/network/client.py
@@ -47,11 +47,17 @@ def make_client(instance):
     endpoint = instance.get_endpoint_for_service_type(
         API_NAME,
         region_name=instance._region_name,
+        endpoint_type=instance._endpoint_type,
     )
 
+    # Remember endpoint_type only if it is set
+    kwargs = utils.build_kwargs_dict('endpoint_type',
+                                     instance._endpoint_type)
+
     client = network_client(
         session=instance.session,
         region_name=instance._region_name,
+        **kwargs
     )
 
     network_api = utils.get_client_class(
diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py
index beb7c04fc1..676f664217 100644
--- a/openstackclient/object/client.py
+++ b/openstackclient/object/client.py
@@ -36,6 +36,7 @@ def make_client(instance):
     endpoint = instance.get_endpoint_for_service_type(
         'object-store',
         region_name=instance._region_name,
+        endpoint_type=instance._endpoint_type,
     )
 
     client = object_store_v1.APIv1(
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index 36483b3a7e..6c9095868b 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -208,6 +208,15 @@ class OpenStackShell(app.App):
             help='Default domain ID, default=' +
                  DEFAULT_DOMAIN +
                  ' (Env: OS_DEFAULT_DOMAIN)')
+        parser.add_argument(
+            '--os-endpoint-type',
+            metavar='<endpoint-type>',
+            dest='endpoint_type',
+            choices=['admin', 'public', 'internal'],
+            default=utils.env('OS_ENDPOINT_TYPE'),
+            help='Select an endpoint type.'
+                 ' Valid endpoint types: [admin, public, internal].'
+                 ' (Env: OS_ENDPOINT_TYPE)')
         parser.add_argument(
             '--timing',
             default=False,
@@ -254,7 +263,10 @@ class OpenStackShell(app.App):
             self.options.project_name = tenant_name
 
         # Do configuration file handling
-        cc = cloud_config.OpenStackConfig()
+        # Ignore the default value of endpoint_type. Only if it is set later
+        # will it be used.
+        cc = cloud_config.OpenStackConfig(
+            override_defaults={'endpoint_type': None, })
         self.log.debug("defaults: %s", cc.defaults)
 
         self.cloud = cc.get_one_cloud(
diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py
index 4e2f46b4ce..e86ef50997 100644
--- a/openstackclient/tests/common/test_clientmanager.py
+++ b/openstackclient/tests/common/test_clientmanager.py
@@ -54,6 +54,7 @@ class FakeOptions(object):
         self.identity_api_version = '2.0'
         self.timing = None
         self.region_name = None
+        self.endpoint_type = None
         self.url = None
         self.auth = {}
         self.default_domain = 'default'
@@ -123,6 +124,8 @@ class TestClientManager(utils.TestCase):
                     auth_url=fakes.AUTH_URL,
                 ),
                 auth_type='v2token',
+                endpoint_type=fakes.ENDPOINT_TYPE,
+                region_name=fakes.REGION_NAME,
             ),
             api_version=API_VERSION,
             verify=True
@@ -137,6 +140,14 @@ class TestClientManager(utils.TestCase):
             client_manager.auth,
             auth_v2.Token,
         )
+        self.assertEqual(
+            fakes.ENDPOINT_TYPE,
+            client_manager._endpoint_type,
+        )
+        self.assertEqual(
+            fakes.REGION_NAME,
+            client_manager._region_name,
+        )
         self.assertFalse(client_manager._insecure)
         self.assertTrue(client_manager._verify)
 
diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py
index d9f5b7a56e..a25a5ba510 100644
--- a/openstackclient/tests/common/test_utils.py
+++ b/openstackclient/tests/common/test_utils.py
@@ -159,6 +159,16 @@ class TestUtils(test_utils.TestCase):
         self.assertFalse(utils.wait_for_delete(manager, res_id))
         self.assertFalse(mock_sleep.called)
 
+    def test_build_kwargs_dict_value_set(self):
+        self.assertEqual({'arg_bla': 'bla'},
+                         utils.build_kwargs_dict('arg_bla', 'bla'))
+
+    def test_build_kwargs_dict_value_None(self):
+        self.assertEqual({}, utils.build_kwargs_dict('arg_bla', None))
+
+    def test_build_kwargs_dict_value_empty_str(self):
+        self.assertEqual({}, utils.build_kwargs_dict('arg_bla', ''))
+
 
 class NoUniqueMatch(Exception):
     pass
diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py
index 323f954306..a9322ec3ed 100644
--- a/openstackclient/tests/fakes.py
+++ b/openstackclient/tests/fakes.py
@@ -26,6 +26,8 @@ AUTH_URL = "http://0.0.0.0"
 USERNAME = "itchy"
 PASSWORD = "scratchy"
 PROJECT_NAME = "poochie"
+REGION_NAME = "richie"
+ENDPOINT_TYPE = "catchy"
 
 TEST_RESPONSE_DICT = fixture.V2Token(token_id=AUTH_TOKEN,
                                      user_name=USERNAME)
diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py
index b080ae9164..674d83452d 100644
--- a/openstackclient/tests/test_shell.py
+++ b/openstackclient/tests/test_shell.py
@@ -38,6 +38,7 @@ DEFAULT_REGION_NAME = "ZZ9_Plural_Z_Alpha"
 DEFAULT_TOKEN = "token"
 DEFAULT_SERVICE_URL = "http://127.0.0.1:8771/v3.0/"
 DEFAULT_AUTH_PLUGIN = "v2password"
+DEFAULT_ENDPOINT_TYPE = "internal"
 
 DEFAULT_COMPUTE_API_VERSION = "2"
 DEFAULT_IDENTITY_API_VERSION = "2"
@@ -61,6 +62,7 @@ CLOUD_1 = {
             },
             'region_name': 'occ-cloud',
             'donut': 'glazed',
+            'endpoint_type': 'public',
         }
     }
 }
@@ -104,6 +106,7 @@ global_options = {
     '--os-default-domain': (DEFAULT_DOMAIN_NAME, True, True),
     '--os-cacert': ('/dev/null', True, True),
     '--timing': (True, True, False),
+    '--os-endpoint-type': (DEFAULT_ENDPOINT_TYPE, True, True)
 }
 
 auth_options = {
@@ -123,6 +126,7 @@ auth_options = {
     '--os-auth-type': ("v2password", True, True),
     '--os-token': (DEFAULT_TOKEN, True, True),
     '--os-url': (DEFAULT_SERVICE_URL, True, True),
+    '--os-endpoint-type': (DEFAULT_ENDPOINT_TYPE, True, True),
 }
 
 
@@ -608,6 +612,10 @@ class TestShellCli(TestShell):
             'glazed',
             _shell.cloud.config['donut'],
         )
+        self.assertEqual(
+            'public',
+            _shell.cloud.config['endpoint_type'],
+        )
 
     @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
     @mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py
index 1038c407e4..965c42ec2d 100644
--- a/openstackclient/volume/client.py
+++ b/openstackclient/volume/client.py
@@ -53,11 +53,16 @@ def make_client(instance):
 
     extensions = [extension.Extension('list_extensions', list_extensions)]
 
+    # Remember endpoint_type only if it is set
+    kwargs = utils.build_kwargs_dict('endpoint_type',
+                                     instance._endpoint_type)
+
     client = volume_client(
         session=instance.session,
         extensions=extensions,
         http_log_debug=http_log_debug,
         region_name=instance._region_name,
+        **kwargs
     )
 
     return client