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` Additional API version options will be available depending on the installed API libraries. +:option:`--os-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='', + 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