Skip to content

Commit 5649695

Browse files
Dean Troyeremonty
andcommitted
Add --os-cloud support
This adds a new option --os-cloud that allows the configuration values for multiple clouds to be stored in a local file and selected with a single option. Internal option names have had 'os_' removed to be comptible with the options returned from OpenStackConfig().get_one_cloud(). The config file is ~/.config/openstack/clouds.yaml: Sample ------ clouds: devstack: auth: auth_url: http://192.168.122.10:35357/ project_name: demo username: demo password: 0penstack region_name: RegionOne devstack: auth: auth_url: http://192.168.122.10:35357/ project_name: demo username: demo password: 0penstack region_name: RegionOne Co-Authored-By: Monty Taylor <mordred@inaugust.com> Change-Id: I4939acf8067e44ffe06a2e26fc28f1adf8985b7d Depends-On: I45e2550af58aee616ca168d20a557077beeab007
1 parent a5e79d5 commit 5649695

9 files changed

Lines changed: 411 additions & 321 deletions

File tree

examples/common.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,17 @@ def base_parser(parser):
8484
"""
8585

8686
# Global arguments
87+
parser.add_argument(
88+
'--os-cloud',
89+
metavar='<cloud-config-name>',
90+
dest='cloud',
91+
default=env('OS_CLOUD'),
92+
help='Cloud name in clouds.yaml (Env: OS_CLOUD)',
93+
)
8794
parser.add_argument(
8895
'--os-region-name',
8996
metavar='<auth-region-name>',
97+
dest='region_name',
9098
default=env('OS_REGION_NAME'),
9199
help='Authentication region name (Env: OS_REGION_NAME)',
92100
)

examples/object_api.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,24 @@
3030
from openstackclient.api import object_store_v1 as object_store
3131
from openstackclient.identity import client as identity_client
3232

33+
from os_client_config import config as cloud_config
34+
3335

3436
LOG = logging.getLogger('')
3537

3638

3739
def run(opts):
3840
"""Run the examples"""
3941

42+
# Look for configuration file
43+
# To support token-flow we have no required values
44+
# print "options: %s" % self.options
45+
cloud = cloud_config.OpenStackConfig().get_one_cloud(
46+
cloud=opts.cloud,
47+
argparse=opts,
48+
)
49+
LOG.debug("cloud cfg: %s", cloud.config)
50+
4051
# Set up certificate verification and CA bundle
4152
# NOTE(dtroyer): This converts from the usual OpenStack way to the single
4253
# requests argument and is an app-specific thing because
@@ -52,13 +63,13 @@ def run(opts):
5263
# The returned session will have a configured auth object
5364
# based on the selected plugin's available options.
5465
# So to do...oh, just go to api.auth.py and look at what it does.
55-
session = common.make_session(opts, verify=verify)
66+
session = common.make_session(cloud, verify=verify)
5667

5768
# Extract an endpoint
5869
auth_ref = session.auth.get_auth_ref(session)
5970

60-
if opts.os_url:
61-
endpoint = opts.os_url
71+
if opts.url:
72+
endpoint = opts.url
6273
else:
6374
endpoint = auth_ref.service_catalog.url_for(
6475
service_type='object-store',

examples/osc-lib.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,25 @@
2929

3030
from openstackclient.common import clientmanager
3131

32+
from os_client_config import config as cloud_config
33+
3234

3335
LOG = logging.getLogger('')
3436

3537

3638
def run(opts):
3739
"""Run the examples"""
3840

41+
# Do configuration file handling
42+
cc = cloud_config.OpenStackConfig()
43+
LOG.debug("defaults: %s", cc.defaults)
44+
45+
cloud = cc.get_one_cloud(
46+
cloud=opts.cloud,
47+
argparse=opts,
48+
)
49+
LOG.debug("cloud cfg: %s", cloud.config)
50+
3951
# Loop through extensions to get API versions
4052
# Currently API versions are statically selected. Once discovery
4153
# is working this can go away...
@@ -59,7 +71,7 @@ def run(opts):
5971
# Collect the auth and config options together and give them to
6072
# ClientManager and it will wrangle all of the goons into place.
6173
client_manager = clientmanager.ClientManager(
62-
cli_options=opts,
74+
cli_options=cloud,
6375
verify=verify,
6476
api_version=api_version,
6577
)

openstackclient/api/auth.py

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -77,25 +77,27 @@ def select_auth_plugin(options):
7777

7878
auth_plugin_name = None
7979

80-
if options.os_auth_type in [plugin.name for plugin in get_plugin_list()]:
81-
# A direct plugin name was given, use it
82-
return options.os_auth_type
83-
84-
if options.os_url and options.os_token:
80+
# Do the token/url check first as this must override the default
81+
# 'password' set by os-client-config
82+
# Also, url and token are not copied into o-c-c's auth dict (yet?)
83+
if options.auth.get('url', None) and options.auth.get('token', None):
8584
# service token authentication
8685
auth_plugin_name = 'token_endpoint'
87-
elif options.os_username:
88-
if options.os_identity_api_version == '3':
86+
elif options.auth_type in [plugin.name for plugin in PLUGIN_LIST]:
87+
# A direct plugin name was given, use it
88+
auth_plugin_name = options.auth_type
89+
elif options.auth.get('username', None):
90+
if options.identity_api_version == '3':
8991
auth_plugin_name = 'v3password'
90-
elif options.os_identity_api_version == '2.0':
92+
elif options.identity_api_version.startswith('2'):
9193
auth_plugin_name = 'v2password'
9294
else:
9395
# let keystoneclient figure it out itself
9496
auth_plugin_name = 'osc_password'
95-
elif options.os_token:
96-
if options.os_identity_api_version == '3':
97+
elif options.auth.get('token', None):
98+
if options.identity_api_version == '3':
9799
auth_plugin_name = 'v3token'
98-
elif options.os_identity_api_version == '2.0':
100+
elif options.identity_api_version.startswith('2'):
99101
auth_plugin_name = 'v2token'
100102
else:
101103
# let keystoneclient figure it out itself
@@ -109,35 +111,27 @@ def select_auth_plugin(options):
109111

110112

111113
def build_auth_params(auth_plugin_name, cmd_options):
112-
auth_params = {}
114+
115+
auth_params = dict(cmd_options.auth)
113116
if auth_plugin_name:
114117
LOG.debug('auth_type: %s', auth_plugin_name)
115118
auth_plugin_class = base.get_plugin_class(auth_plugin_name)
116-
plugin_options = auth_plugin_class.get_options()
117-
for option in plugin_options:
118-
option_name = 'os_' + option.dest
119-
LOG.debug('fetching option %s' % option_name)
120-
auth_params[option.dest] = getattr(cmd_options, option_name, None)
121119
# grab tenant from project for v2.0 API compatibility
122120
if auth_plugin_name.startswith("v2"):
123-
auth_params['tenant_id'] = getattr(
124-
cmd_options,
125-
'os_project_id',
126-
None,
127-
)
128-
auth_params['tenant_name'] = getattr(
129-
cmd_options,
130-
'os_project_name',
131-
None,
132-
)
121+
if 'project_id' in auth_params:
122+
auth_params['tenant_id'] = auth_params['project_id']
123+
del auth_params['project_id']
124+
if 'project_name' in auth_params:
125+
auth_params['tenant_name'] = auth_params['project_name']
126+
del auth_params['project_name']
133127
else:
134128
LOG.debug('no auth_type')
135129
# delay the plugin choice, grab every option
130+
auth_plugin_class = None
136131
plugin_options = set([o.replace('-', '_') for o in get_options_list()])
137132
for option in plugin_options:
138-
option_name = 'os_' + option
139-
LOG.debug('fetching option %s' % option_name)
140-
auth_params[option] = getattr(cmd_options, option_name, None)
133+
LOG.debug('fetching option %s' % option)
134+
auth_params[option] = getattr(cmd_options.auth, option, None)
141135
return (auth_plugin_class, auth_params)
142136

143137

@@ -146,15 +140,29 @@ def check_valid_auth_options(options, auth_plugin_name):
146140

147141
msg = ''
148142
if auth_plugin_name.endswith('password'):
149-
if not options.os_username:
150-
msg += _('Set a username with --os-username or OS_USERNAME\n')
151-
if not options.os_auth_url:
152-
msg += _('Set an authentication URL, with --os-auth-url or'
153-
' OS_AUTH_URL\n')
154-
if (not options.os_project_id and not options.os_domain_id and not
155-
options.os_domain_name and not options.os_project_name):
143+
if not options.auth.get('username', None):
144+
msg += _('Set a username with --os-username, OS_USERNAME,'
145+
' or auth.username\n')
146+
if not options.auth.get('auth_url', None):
147+
msg += _('Set an authentication URL, with --os-auth-url,'
148+
' OS_AUTH_URL or auth.auth_url\n')
149+
if (not options.auth.get('project_id', None) and not
150+
options.auth.get('domain_id', None) and not
151+
options.auth.get('domain_name', None) and not
152+
options.auth.get('project_name', None)):
156153
msg += _('Set a scope, such as a project or domain, with '
157-
'--os-project-name or OS_PROJECT_NAME')
154+
'--os-project-name, OS_PROJECT_NAME or auth.project_name')
155+
elif auth_plugin_name.endswith('token'):
156+
if not options.auth.get('token', None):
157+
msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n')
158+
if not options.auth.get('auth_url', None):
159+
msg += _('Set a service AUTH_URL, with --os-auth-url, '
160+
'OS_AUTH_URL or auth.auth_url\n')
161+
elif auth_plugin_name == 'token_endpoint':
162+
if not options.auth.get('token', None):
163+
msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n')
164+
if not options.auth.get('url', None):
165+
msg += _('Set a service URL, with --os-url, OS_URL or auth.url\n')
158166

159167
if msg:
160168
raise exc.CommandError('Missing parameter(s): \n%s' % msg)
@@ -171,14 +179,15 @@ def build_auth_plugins_option_parser(parser):
171179
parser.add_argument(
172180
'--os-auth-type',
173181
metavar='<auth-type>',
182+
dest='auth_type',
174183
default=utils.env('OS_AUTH_TYPE'),
175184
help='Select an auhentication type. Available types: ' +
176185
', '.join(available_plugins) +
177186
'. Default: selected based on --os-username/--os-token' +
178187
' (Env: OS_AUTH_TYPE)',
179188
choices=available_plugins
180189
)
181-
# make sure we catch old v2.0 env values
190+
# Maintain compatibility with old tenant env vars
182191
envs = {
183192
'OS_PROJECT_NAME': utils.env(
184193
'OS_PROJECT_NAME',
@@ -190,30 +199,33 @@ def build_auth_plugins_option_parser(parser):
190199
),
191200
}
192201
for o in get_options_list():
193-
# remove allusion to tenants from v2.0 API
202+
# Remove tenant options from KSC plugins and replace them below
194203
if 'tenant' not in o:
195204
parser.add_argument(
196205
'--os-' + o,
197206
metavar='<auth-%s>' % o,
198-
default=envs.get(OPTIONS_LIST[o]['env'],
199-
utils.env(OPTIONS_LIST[o]['env'])),
200-
help='%s\n(Env: %s)' % (OPTIONS_LIST[o]['help'],
201-
OPTIONS_LIST[o]['env']),
207+
dest=o.replace('-', '_'),
208+
default=envs.get(
209+
OPTIONS_LIST[o]['env'],
210+
utils.env(OPTIONS_LIST[o]['env']),
211+
),
212+
help='%s\n(Env: %s)' % (
213+
OPTIONS_LIST[o]['help'],
214+
OPTIONS_LIST[o]['env'],
215+
),
202216
)
203217
# add tenant-related options for compatibility
204218
# this is deprecated but still used in some tempest tests...
205219
parser.add_argument(
206220
'--os-tenant-name',
207221
metavar='<auth-tenant-name>',
208222
dest='os_project_name',
209-
default=utils.env('OS_TENANT_NAME'),
210223
help=argparse.SUPPRESS,
211224
)
212225
parser.add_argument(
213226
'--os-tenant-id',
214227
metavar='<auth-tenant-id>',
215228
dest='os_project_id',
216-
default=utils.env('OS_TENANT_ID'),
217229
help=argparse.SUPPRESS,
218230
)
219231
return parser

openstackclient/common/clientmanager.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def __getattr__(self, name):
5858

5959
def __init__(
6060
self,
61-
cli_options,
61+
cli_options=None,
6262
api_version=None,
6363
verify=True,
6464
pw_func=None,
@@ -82,8 +82,8 @@ def __init__(
8282
self._cli_options = cli_options
8383
self._api_version = api_version
8484
self._pw_callback = pw_func
85-
self._url = self._cli_options.os_url
86-
self._region_name = self._cli_options.os_region_name
85+
self._url = self._cli_options.auth.get('url', None)
86+
self._region_name = self._cli_options.region_name
8787

8888
self.timing = self._cli_options.timing
8989

@@ -121,30 +121,32 @@ def setup_auth(self):
121121
# Horrible hack alert...must handle prompt for null password if
122122
# password auth is requested.
123123
if (self.auth_plugin_name.endswith('password') and
124-
not self._cli_options.os_password):
124+
not self._cli_options.auth.get('password', None)):
125125
self._cli_options.os_password = self._pw_callback()
126126

127127
(auth_plugin, self._auth_params) = auth.build_auth_params(
128128
self.auth_plugin_name,
129129
self._cli_options,
130130
)
131131

132-
default_domain = self._cli_options.os_default_domain
132+
# TODO(mordred): This is a usability improvement that's broadly useful
133+
# We should port it back up into os-client-config.
134+
default_domain = self._cli_options.default_domain
133135
# NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is
134136
# present, then do not change the behaviour. Otherwise, set the
135137
# PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability.
136138
if (self._api_version.get('identity') == '3' and
137-
not self._auth_params.get('project_domain_id') and
138-
not self._auth_params.get('project_domain_name')):
139+
not self._auth_params.get('project_domain_id', None) and
140+
not self._auth_params.get('project_domain_name', None)):
139141
self._auth_params['project_domain_id'] = default_domain
140142

141143
# NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present,
142144
# then do not change the behaviour. Otherwise, set the USER_DOMAIN_ID
143145
# to 'OS_DEFAULT_DOMAIN' for better usability.
144146
if (self._api_version.get('identity') == '3' and
145147
self.auth_plugin_name.endswith('password') and
146-
not self._auth_params.get('user_domain_id') and
147-
not self._auth_params.get('user_domain_name')):
148+
not self._auth_params.get('user_domain_id', None) and
149+
not self._auth_params.get('user_domain_name', None)):
148150
self._auth_params['user_domain_id'] = default_domain
149151

150152
# For compatibility until all clients can be updated

0 commit comments

Comments
 (0)