Add method for registering argparse options

keystoneauth knows about a bunch of argparse options that users
from a command line will want. We do a good job of processing them
once they've been collected, but an os-client-config user doesn't
have a great way to make sure that they register all of the options,
especially when once considers that you really want to peek at the
args to see which auth plugin has been selected so that the right
arguments can be registered and displayed.

Depends-On: Ifea90b981044009c3642b268dd639a703df1ef05
Change-Id: Ic196f65f89b3ccf92ebec39564f5eaefe8a4ae4b
This commit is contained in:
Monty Taylor 2015-11-04 09:22:17 -05:00
parent 3ceee61181
commit ed2f34b06a
4 changed files with 263 additions and 1 deletions

View File

@ -296,3 +296,26 @@ Or, get all of the clouds.
cloud_config = os_client_config.OpenStackConfig().get_all_clouds()
for cloud in cloud_config:
print(cloud.name, cloud.region, cloud.config)
argparse
--------
If you're using os-client-config from a program that wants to process
command line options, there is a registration function to register the
arguments that both os-client-config and keystoneauth know how to deal
with - as well as a consumption argument.
::
import argparse
import sys
import os_client_config
cloud_config = os_client_config.OpenStackConfig()
parser = argparse.ArgumentParser()
cloud_config.register_argparse_arguments(parser, sys.argv)
options = parser.parse_args()
cloud = cloud_config.get_one_cloud(argparse=options)

View File

@ -13,11 +13,14 @@
# under the License.
# alias because we already had an option named argparse
import argparse as argparse_mod
import json
import os
import warnings
import appdirs
from keystoneauth1 import adapter
from keystoneauth1 import loading
import yaml
@ -245,6 +248,9 @@ class OpenStackConfig(object):
self._cache_expiration = cache_settings.get(
'expiration', self._cache_expiration)
# Flag location to hold the peeked value of an argparse timeout value
self._argv_timeout = False
def _load_config_file(self):
return self._load_yaml_json_file(self._config_files)
@ -451,6 +457,94 @@ class OpenStackConfig(object):
cloud['auth_type'] = 'password'
return cloud
def register_argparse_arguments(self, parser, argv, service_keys=[]):
"""Register all of the common argparse options needed.
Given an argparse parser, register the keystoneauth Session arguments,
the keystoneauth Auth Plugin Options and os-cloud. Also, peek in the
argv to see if all of the auth plugin options should be registered
or merely the ones already configured.
:param argparse.ArgumentParser: parser to attach argparse options to
:param list argv: the arguments provided to the application
:param string service_keys: Service or list of services this argparse
should be specialized for, if known.
The first item in the list will be used
as the default value for service_type
(optional)
:raises exceptions.OpenStackConfigException if an invalid auth-type
is requested
"""
local_parser = argparse_mod.ArgumentParser(add_help=False)
for p in (parser, local_parser):
p.add_argument(
'--os-cloud',
metavar='<name>',
default=os.environ.get('OS_CLOUD', None),
help='Named cloud to connect to')
# we need to peek to see if timeout was actually passed, since
# the keystoneauth declaration of it has a default, which means
# we have no clue if the value we get is from the ksa default
# for from the user passing it explicitly. We'll stash it for later
local_parser.add_argument('--timeout', metavar='<timeout>')
# Peek into the future and see if we have an auth-type set in
# config AND a cloud set, so that we know which command line
# arguments to register and show to the user (the user may want
# to say something like:
# openstack --os-cloud=foo --os-oidctoken=bar
# although I think that user is the cause of my personal pain
options, _args = local_parser.parse_known_args(argv)
if options.timeout:
self._argv_timeout = True
# validate = False because we're not _actually_ loading here
# we're only peeking, so it's the wrong time to assert that
# the rest of the arguments given are invalid for the plugin
# chosen (for instance, --help may be requested, so that the
# user can see what options he may want to give
cloud = self.get_one_cloud(argparse=options, validate=False)
default_auth_type = cloud.config['auth_type']
try:
loading.register_auth_argparse_arguments(
parser, argv, default=default_auth_type)
except Exception:
# Hidiing the keystoneauth exception because we're not actually
# loading the auth plugin at this point, so the error message
# from it doesn't actually make sense to os-client-config users
options, _args = parser.parse_known_args(argv)
plugin_names = loading.get_available_plugin_names()
raise exceptions.OpenStackConfigException(
"An invalid auth-type was specified: {auth_type}."
" Valid choices are: {plugin_names}.".format(
auth_type=options.os_auth_type,
plugin_names=",".join(plugin_names)))
if service_keys:
primary_service = service_keys[0]
else:
primary_service = None
loading.register_session_argparse_arguments(parser)
adapter.register_adapter_argparse_arguments(
parser, service_type=primary_service)
for service_key in service_keys:
# legacy clients have un-prefixed api-version options
parser.add_argument(
'--{service_key}-api-version'.format(
service_key=service_key.replace('_', '-'),
help=argparse_mod.SUPPRESS))
adapter.register_service_adapter_argparse_arguments(
parser, service_type=service_key)
# Backwards compat options for legacy clients
parser.add_argument('--http-timeout', help=argparse_mod.SUPPRESS)
parser.add_argument('--os-endpoint-type', help=argparse_mod.SUPPRESS)
parser.add_argument('--endpoint-type', help=argparse_mod.SUPPRESS)
def _fix_backwards_interface(self, cloud):
new_cloud = {}
for key in cloud.keys():
@ -461,6 +555,30 @@ class OpenStackConfig(object):
new_cloud[target_key] = cloud[key]
return new_cloud
def _fix_backwards_api_timeout(self, cloud):
new_cloud = {}
# requests can only have one timeout, which means that in a single
# cloud there is no point in different timeout values. However,
# for some reason many of the legacy clients decided to shove their
# service name in to the arg name for reasons surpassin sanity. If
# we find any values that are not api_timeout, overwrite api_timeout
# with the value
service_timeout = None
for key in cloud.keys():
if key.endswith('timeout') and not (
key == 'timeout' or key == 'api_timeout'):
service_timeout = cloud[key]
else:
new_cloud[key] = cloud[key]
if service_timeout is not None:
new_cloud['api_timeout'] = service_timeout
# The common argparse arg from keystoneauth is called timeout, but
# os-client-config expects it to be called api_timeout
if self._argv_timeout:
if 'timeout' in new_cloud and new_cloud['timeout']:
new_cloud['api_timeout'] = new_cloud.pop('timeout')
return new_cloud
def get_all_clouds(self):
clouds = []
@ -671,6 +789,12 @@ class OpenStackConfig(object):
else:
config[key] = val
# These backwards compat values are only set via argparse. If it's
# there, it's because it was passed in explicitly, and should win
config = self._fix_backwards_api_timeout(config)
if 'endpoint_type' in config:
config['interface'] = config.pop('endpoint_type')
for key in BOOL_KEYS:
if key in config:
if type(config[key]) is not bool:

View File

@ -17,6 +17,7 @@ import copy
import os
import fixtures
import testtools
import yaml
from os_client_config import cloud_config
@ -341,6 +342,120 @@ class TestConfigArgparse(base.TestCase):
self.assertDictEqual({'compute_api_version': 1}, fixed_args)
def test_register_argparse_cloud(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
parser = argparse.ArgumentParser()
c.register_argparse_arguments(parser, [])
opts, _remain = parser.parse_known_args(['--os-cloud', 'foo'])
self.assertEqual(opts.os_cloud, 'foo')
def test_register_argparse_bad_plugin(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
parser = argparse.ArgumentParser()
self.assertRaises(
exceptions.OpenStackConfigException,
c.register_argparse_arguments,
parser, ['--os-auth-type', 'foo'])
def test_register_argparse_not_password(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
parser = argparse.ArgumentParser()
args = [
'--os-auth-type', 'v3token',
'--os-token', 'some-secret',
]
c.register_argparse_arguments(parser, args)
opts, _remain = parser.parse_known_args(args)
self.assertEqual(opts.os_token, 'some-secret')
def test_register_argparse_password(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
parser = argparse.ArgumentParser()
args = [
'--os-password', 'some-secret',
]
c.register_argparse_arguments(parser, args)
opts, _remain = parser.parse_known_args(args)
self.assertEqual(opts.os_password, 'some-secret')
with testtools.ExpectedException(AttributeError):
opts.os_token
def test_register_argparse_service_type(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
parser = argparse.ArgumentParser()
args = [
'--os-service-type', 'network',
'--os-endpoint-type', 'admin',
'--http-timeout', '20',
]
c.register_argparse_arguments(parser, args)
opts, _remain = parser.parse_known_args(args)
self.assertEqual(opts.os_service_type, 'network')
self.assertEqual(opts.os_endpoint_type, 'admin')
self.assertEqual(opts.http_timeout, '20')
with testtools.ExpectedException(AttributeError):
opts.os_network_service_type
cloud = c.get_one_cloud(argparse=opts, verify=False)
self.assertEqual(cloud.config['service_type'], 'network')
self.assertEqual(cloud.config['interface'], 'admin')
self.assertEqual(cloud.config['api_timeout'], '20')
self.assertNotIn('http_timeout', cloud.config)
def test_register_argparse_network_service_type(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
parser = argparse.ArgumentParser()
args = [
'--os-endpoint-type', 'admin',
'--network-api-version', '4',
]
c.register_argparse_arguments(parser, args, ['network'])
opts, _remain = parser.parse_known_args(args)
self.assertEqual(opts.os_service_type, 'network')
self.assertEqual(opts.os_endpoint_type, 'admin')
self.assertEqual(opts.os_network_service_type, None)
self.assertEqual(opts.os_network_api_version, None)
self.assertEqual(opts.network_api_version, '4')
cloud = c.get_one_cloud(argparse=opts, verify=False)
self.assertEqual(cloud.config['service_type'], 'network')
self.assertEqual(cloud.config['interface'], 'admin')
self.assertEqual(cloud.config['network_api_version'], '4')
self.assertNotIn('http_timeout', cloud.config)
def test_register_argparse_network_service_types(self):
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
vendor_files=[self.vendor_yaml])
parser = argparse.ArgumentParser()
args = [
'--os-compute-service-name', 'cloudServers',
'--os-network-service-type', 'badtype',
'--os-endpoint-type', 'admin',
'--network-api-version', '4',
]
c.register_argparse_arguments(
parser, args, ['compute', 'network', 'volume'])
opts, _remain = parser.parse_known_args(args)
self.assertEqual(opts.os_network_service_type, 'badtype')
self.assertEqual(opts.os_compute_service_type, None)
self.assertEqual(opts.os_volume_service_type, None)
self.assertEqual(opts.os_service_type, 'compute')
self.assertEqual(opts.os_compute_service_name, 'cloudServers')
self.assertEqual(opts.os_endpoint_type, 'admin')
self.assertEqual(opts.os_network_api_version, None)
self.assertEqual(opts.network_api_version, '4')
cloud = c.get_one_cloud(argparse=opts, verify=False)
self.assertEqual(cloud.config['service_type'], 'compute')
self.assertEqual(cloud.config['network_service_type'], 'badtype')
self.assertEqual(cloud.config['interface'], 'admin')
self.assertEqual(cloud.config['network_api_version'], '4')
self.assertNotIn('volume_service_type', cloud.config)
self.assertNotIn('http_timeout', cloud.config)
class TestConfigDefault(base.TestCase):

View File

@ -3,5 +3,5 @@
# process, which may cause wedges in the gate later.
PyYAML>=3.1.0
appdirs>=1.3.0
keystoneauth1>=1.0.0
keystoneauth1>=2.1.0
requestsexceptions>=1.1.1 # Apache-2.0