Skip to content

Commit 9062811

Browse files
author
Dean Troyer
committed
Expand support for command extensions
Allows client libraries to have complete access to the rest of the OSC ClientManager. In addition, extension libraries can define global options (for API version options/env vars) and define versioned API entry points similar to the in-repo commands. The changes to ClientManager exposed some issues in the existing object api tests that needed to be cleaned up. Change-Id: Ic9662edf34c5dd130a2f1a69d2454adefc1f8a95
1 parent d45187a commit 9062811

14 files changed

Lines changed: 172 additions & 119 deletions

File tree

openstackclient/common/clientmanager.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@
1616
"""Manage access to the clients, including authenticating when needed."""
1717

1818
import logging
19+
import pkg_resources
20+
import sys
1921

20-
from openstackclient.compute import client as compute_client
2122
from openstackclient.identity import client as identity_client
22-
from openstackclient.image import client as image_client
23-
from openstackclient.object import client as object_client
24-
from openstackclient.volume import client as volume_client
2523

2624

2725
LOG = logging.getLogger(__name__)
@@ -42,11 +40,7 @@ def __get__(self, instance, owner):
4240

4341
class ClientManager(object):
4442
"""Manages access to API clients, including authentication."""
45-
compute = ClientCache(compute_client.make_client)
4643
identity = ClientCache(identity_client.make_client)
47-
image = ClientCache(image_client.make_client)
48-
object = ClientCache(object_client.make_client)
49-
volume = ClientCache(volume_client.make_client)
5044

5145
def __init__(self, token=None, url=None, auth_url=None, project_name=None,
5246
project_id=None, username=None, password=None,
@@ -93,3 +87,26 @@ def get_endpoint_for_service_type(self, service_type):
9387
# Hope we were given the correct URL.
9488
endpoint = self._url
9589
return endpoint
90+
91+
92+
def get_extension_modules(group):
93+
"""Add extension clients"""
94+
mod_list = []
95+
for ep in pkg_resources.iter_entry_points(group):
96+
LOG.debug('found extension %r' % ep.name)
97+
98+
__import__(ep.module_name)
99+
module = sys.modules[ep.module_name]
100+
mod_list.append(module)
101+
init_func = getattr(module, 'Initialize', None)
102+
if init_func:
103+
init_func('x')
104+
105+
setattr(
106+
ClientManager,
107+
ep.name,
108+
ClientCache(
109+
getattr(sys.modules[ep.module_name], 'make_client', None)
110+
),
111+
)
112+
return mod_list

openstackclient/compute/client.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
LOG = logging.getLogger(__name__)
2121

22+
DEFAULT_COMPUTE_API_VERSION = '2'
23+
API_VERSION_OPTION = 'os_compute_api_version'
2224
API_NAME = 'compute'
2325
API_VERSIONS = {
2426
'1.1': 'novaclient.v1_1.client.Client',
@@ -60,3 +62,17 @@ def make_client(instance):
6062
client.client.service_catalog = instance._service_catalog
6163
client.client.auth_token = instance._token
6264
return client
65+
66+
67+
def build_option_parser(parser):
68+
"""Hook to add global options"""
69+
parser.add_argument(
70+
'--os-compute-api-version',
71+
metavar='<compute-api-version>',
72+
default=utils.env(
73+
'OS_COMPUTE_API_VERSION',
74+
default=DEFAULT_COMPUTE_API_VERSION),
75+
help='Compute API version, default=' +
76+
DEFAULT_COMPUTE_API_VERSION +
77+
' (Env: OS_COMPUTE_API_VERSION)')
78+
return parser

openstackclient/identity/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
LOG = logging.getLogger(__name__)
2323

24+
DEFAULT_IDENTITY_API_VERSION = '2.0'
25+
API_VERSION_OPTION = 'os_identity_api_version'
2426
API_NAME = 'identity'
2527
API_VERSIONS = {
2628
'2.0': 'openstackclient.identity.client.IdentityClientv2_0',

openstackclient/image/client.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
LOG = logging.getLogger(__name__)
2525

26+
DEFAULT_IMAGE_API_VERSION = '1'
27+
API_VERSION_OPTION = 'os_image_api_version'
2628
API_NAME = "image"
2729
API_VERSIONS = {
2830
"1": "openstackclient.image.client.Client_v1",
@@ -48,6 +50,20 @@ def make_client(instance):
4850
)
4951

5052

53+
def build_option_parser(parser):
54+
"""Hook to add global options"""
55+
parser.add_argument(
56+
'--os-image-api-version',
57+
metavar='<image-api-version>',
58+
default=utils.env(
59+
'OS_IMAGE_API_VERSION',
60+
default=DEFAULT_IMAGE_API_VERSION),
61+
help='Image API version, default=' +
62+
DEFAULT_IMAGE_API_VERSION +
63+
' (Env: OS_IMAGE_API_VERSION)')
64+
return parser
65+
66+
5167
# NOTE(dtroyer): glanceclient.v1.image.ImageManager() doesn't have a find()
5268
# method so add one here until the common client libs arrive
5369
# A similar subclass will be required for v2

openstackclient/object/client.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
LOG = logging.getLogger(__name__)
2323

24-
API_NAME = 'object-store'
24+
DEFAULT_OBJECT_API_VERSION = '1'
25+
API_VERSION_OPTION = 'os_object_api_version'
26+
API_NAME = 'object'
2527
API_VERSIONS = {
2628
'1': 'openstackclient.object.client.ObjectClientv1',
2729
}
@@ -45,6 +47,20 @@ def make_client(instance):
4547
return client
4648

4749

50+
def build_option_parser(parser):
51+
"""Hook to add global options"""
52+
parser.add_argument(
53+
'--os-object-api-version',
54+
metavar='<object-api-version>',
55+
default=utils.env(
56+
'OS_OBJECT_API_VERSION',
57+
default=DEFAULT_OBJECT_API_VERSION),
58+
help='Object API version, default=' +
59+
DEFAULT_OBJECT_API_VERSION +
60+
' (Env: OS_OBJECT_API_VERSION)')
61+
return parser
62+
63+
4864
class ObjectClientv1(object):
4965

5066
def __init__(

openstackclient/shell.py

Lines changed: 29 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,11 @@
3333
from openstackclient.common import openstackkeyring
3434
from openstackclient.common import restapi
3535
from openstackclient.common import utils
36+
from openstackclient.identity import client as identity_client
3637

3738

3839
KEYRING_SERVICE = 'openstack'
3940

40-
DEFAULT_COMPUTE_API_VERSION = '2'
41-
DEFAULT_IDENTITY_API_VERSION = '2.0'
42-
DEFAULT_IMAGE_API_VERSION = '1'
43-
DEFAULT_OBJECT_API_VERSION = '1'
44-
DEFAULT_VOLUME_API_VERSION = '1'
4541
DEFAULT_DOMAIN = 'default'
4642

4743

@@ -86,6 +82,15 @@ def __init__(self):
8682
# Assume TLS host certificate verification is enabled
8783
self.verify = True
8884

85+
# Get list of extension modules
86+
self.ext_modules = clientmanager.get_extension_modules(
87+
'openstack.cli.extension',
88+
)
89+
90+
# Loop through extensions to get parser additions
91+
for mod in self.ext_modules:
92+
self.parser = mod.build_option_parser(self.parser)
93+
8994
# NOTE(dtroyer): This hack changes the help action that Cliff
9095
# automatically adds to the parser so we can defer
9196
# its execution until after the api-versioned commands
@@ -202,51 +207,6 @@ def build_option_parser(self, description, version):
202207
help='Default domain ID, default=' +
203208
DEFAULT_DOMAIN +
204209
' (Env: OS_DEFAULT_DOMAIN)')
205-
parser.add_argument(
206-
'--os-identity-api-version',
207-
metavar='<identity-api-version>',
208-
default=env(
209-
'OS_IDENTITY_API_VERSION',
210-
default=DEFAULT_IDENTITY_API_VERSION),
211-
help='Identity API version, default=' +
212-
DEFAULT_IDENTITY_API_VERSION +
213-
' (Env: OS_IDENTITY_API_VERSION)')
214-
parser.add_argument(
215-
'--os-compute-api-version',
216-
metavar='<compute-api-version>',
217-
default=env(
218-
'OS_COMPUTE_API_VERSION',
219-
default=DEFAULT_COMPUTE_API_VERSION),
220-
help='Compute API version, default=' +
221-
DEFAULT_COMPUTE_API_VERSION +
222-
' (Env: OS_COMPUTE_API_VERSION)')
223-
parser.add_argument(
224-
'--os-image-api-version',
225-
metavar='<image-api-version>',
226-
default=env(
227-
'OS_IMAGE_API_VERSION',
228-
default=DEFAULT_IMAGE_API_VERSION),
229-
help='Image API version, default=' +
230-
DEFAULT_IMAGE_API_VERSION +
231-
' (Env: OS_IMAGE_API_VERSION)')
232-
parser.add_argument(
233-
'--os-object-api-version',
234-
metavar='<object-api-version>',
235-
default=env(
236-
'OS_OBJECT_API_VERSION',
237-
default=DEFAULT_OBJECT_API_VERSION),
238-
help='Object API version, default=' +
239-
DEFAULT_OBJECT_API_VERSION +
240-
' (Env: OS_OBJECT_API_VERSION)')
241-
parser.add_argument(
242-
'--os-volume-api-version',
243-
metavar='<volume-api-version>',
244-
default=env(
245-
'OS_VOLUME_API_VERSION',
246-
default=DEFAULT_VOLUME_API_VERSION),
247-
help='Volume API version, default=' +
248-
DEFAULT_VOLUME_API_VERSION +
249-
' (Env: OS_VOLUME_API_VERSION)')
250210
parser.add_argument(
251211
'--os-token',
252212
metavar='<token>',
@@ -270,6 +230,16 @@ def build_option_parser(self, description, version):
270230
help='Use keyring to store password, '
271231
'default=False (Env: OS_USE_KEYRING)')
272232

233+
parser.add_argument(
234+
'--os-identity-api-version',
235+
metavar='<identity-api-version>',
236+
default=env(
237+
'OS_IDENTITY_API_VERSION',
238+
default=identity_client.DEFAULT_IDENTITY_API_VERSION),
239+
help='Identity API version, default=' +
240+
identity_client.DEFAULT_IDENTITY_API_VERSION +
241+
' (Env: OS_IDENTITY_API_VERSION)')
242+
273243
return parser
274244

275245
def authenticate_user(self):
@@ -391,17 +361,20 @@ def initialize_app(self, argv):
391361

392362
# Stash selected API versions for later
393363
self.api_version = {
394-
'compute': self.options.os_compute_api_version,
395364
'identity': self.options.os_identity_api_version,
396-
'image': self.options.os_image_api_version,
397-
'object-store': self.options.os_object_api_version,
398-
'volume': self.options.os_volume_api_version,
399365
}
366+
# Loop through extensions to get API versions
367+
for mod in self.ext_modules:
368+
ver = getattr(self.options, mod.API_VERSION_OPTION, None)
369+
if ver:
370+
self.api_version[mod.API_NAME] = ver
371+
self.log.debug('%s API version %s' % (mod.API_NAME, ver))
400372

401373
# Add the API version-specific commands
402374
for api in self.api_version.keys():
403375
version = '.v' + self.api_version[api].replace('.', '_')
404376
cmd_group = 'openstack.' + api.replace('-', '_') + version
377+
self.log.debug('command group %s' % cmd_group)
405378
self.command_manager.add_command_group(cmd_group)
406379

407380
# Commands that span multiple APIs
@@ -420,6 +393,8 @@ def initialize_app(self, argv):
420393
# }
421394
self.command_manager.add_command_group(
422395
'openstack.extension')
396+
# call InitializeXxx() here
397+
# set up additional clients to stuff in to client_manager??
423398

424399
# Handle deferred help and exit
425400
if self.options.deferred_help:

openstackclient/tests/fakes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def __init__(self):
4949
self.compute = None
5050
self.identity = None
5151
self.image = None
52+
self.object = None
5253
self.volume = None
5354
self.auth_ref = None
5455

openstackclient/tests/object/v1/fakes.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
# under the License.
1414
#
1515

16+
from openstackclient.tests import fakes
17+
from openstackclient.tests import utils
18+
19+
1620
container_name = 'bit-bucket'
1721
container_bytes = 1024
1822
container_count = 1
@@ -65,3 +69,19 @@
6569
'content_type': object_content_type_2,
6670
'last_modified': object_modified_2,
6771
}
72+
73+
74+
class FakeObjectv1Client(object):
75+
def __init__(self, **kwargs):
76+
self.endpoint = kwargs['endpoint']
77+
self.token = kwargs['token']
78+
79+
80+
class TestObjectv1(utils.TestCommand):
81+
def setUp(self):
82+
super(TestObjectv1, self).setUp()
83+
84+
self.app.client_manager.object = FakeObjectv1Client(
85+
endpoint=fakes.AUTH_URL,
86+
token=fakes.AUTH_TOKEN,
87+
)

0 commit comments

Comments
 (0)