Multiple API version support
* Use multiple entry point groups to represent each API+version combination supported * Add some tests Try it out: * Right now only '* user' commands have multiple overlapping versions; you can see the selection between v2.0 and v3 by looking at the command help output for 'tenant' vs 'project': os --os-identity-api-version=2.0 help set user os --os-identity-api-version=3 help set user Change-Id: I7114fd246843df0243d354a7cce697810bb7de62
This commit is contained in:
		
							
								
								
									
										2
									
								
								HACKING
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								HACKING
									
									
									
									
									
								
							| @@ -39,8 +39,8 @@ Human Alphabetical Order Examples | |||||||
|   import logging |   import logging | ||||||
|   import random |   import random | ||||||
|   import StringIO |   import StringIO | ||||||
|  |   import testtools | ||||||
|   import time |   import time | ||||||
|   import unittest |  | ||||||
|  |  | ||||||
|   from nova import flags |   from nova import flags | ||||||
|   from nova import test |   from nova import test | ||||||
|   | |||||||
							
								
								
									
										60
									
								
								doc/source/commands.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								doc/source/commands.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | ======== | ||||||
|  | Commands | ||||||
|  | ======== | ||||||
|  |  | ||||||
|  | Command Structure | ||||||
|  | ================= | ||||||
|  |  | ||||||
|  | OpenStack Client uses a command form ``verb object``. | ||||||
|  |  | ||||||
|  | Note that 'object' here refers to the target of a command's action.  In coding | ||||||
|  | discussions 'object' has its usual Python meaning.  Go figure. | ||||||
|  |  | ||||||
|  | Commands take the form:: | ||||||
|  |  | ||||||
|  |     openstack [<global-options>] <verb> <object> [<command-local-arguments>] | ||||||
|  |  | ||||||
|  | Command Arguments | ||||||
|  | ----------------- | ||||||
|  |  | ||||||
|  |   * All long option names use two dashes ('--') as the prefix and a single dash | ||||||
|  |     ('-') as the interpolation character.  Some common options also have the | ||||||
|  |     traditional single letter name prefixed by a single dash ('-'). | ||||||
|  |   * Global options generally have a corresponding environment variable that | ||||||
|  |     may also be used to set the value. If both are present, the command-line | ||||||
|  |     option takes priority. The environment variable names can be derived from | ||||||
|  |     the option name by dropping the leading '--', converting all embedded dashes | ||||||
|  |     ('-') to underscores ('_'), and converting the name to upper case. | ||||||
|  |   * Positional arguments trail command options. In commands that require two or | ||||||
|  |     more objects be acted upon, such as 'attach A to B', both objects appear | ||||||
|  |     as positional arguments. If they also appear in the command object they are | ||||||
|  |     in the same order. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Implementation | ||||||
|  | ============== | ||||||
|  |  | ||||||
|  | The command structure is designed to support seamless addition of extension | ||||||
|  | command modules via entry points.  The extensions are assumed to be subclasses | ||||||
|  | of Cliff's command.Command object. | ||||||
|  |  | ||||||
|  | Command Entry Points | ||||||
|  | -------------------- | ||||||
|  |  | ||||||
|  | Commands are added to the client using distribute's entry points in ``setup.py``. | ||||||
|  | There is a single common group ``openstack.cli`` for commands that are not versioned, | ||||||
|  | and a group for each combination of OpenStack API and version that is | ||||||
|  | supported.  For example, to support Identity API v3 there is a group called | ||||||
|  | ``openstack.identity.v3`` that contains the individual commands.  The command | ||||||
|  | entry points have the form:: | ||||||
|  |  | ||||||
|  |     "verb_object=fully.qualified.module.vXX.object:VerbObject" | ||||||
|  |  | ||||||
|  | For example, the 'list user' command fir the Identity API is identified in | ||||||
|  | ``setup.py`` with:: | ||||||
|  |  | ||||||
|  |     'openstack.identity.v3': [ | ||||||
|  |         # ... | ||||||
|  |         'list_user=openstackclient.identity.v3.user:ListUser', | ||||||
|  |         # ... | ||||||
|  |     ], | ||||||
							
								
								
									
										42
									
								
								openstackclient/common/commandmanager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								openstackclient/common/commandmanager.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | #   Copyright 2012-2013 OpenStack, LLC. | ||||||
|  | # | ||||||
|  | #   Licensed under the Apache License, Version 2.0 (the "License"); you may | ||||||
|  | #   not use this file except in compliance with the License. You may obtain | ||||||
|  | #   a copy of the License at | ||||||
|  | # | ||||||
|  | #        http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | #   Unless required by applicable law or agreed to in writing, software | ||||||
|  | #   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||||
|  | #   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||||
|  | #   License for the specific language governing permissions and limitations | ||||||
|  | #   under the License. | ||||||
|  | # | ||||||
|  |  | ||||||
|  | """Modify Cliff's CommandManager""" | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | import pkg_resources | ||||||
|  |  | ||||||
|  | import cliff.commandmanager | ||||||
|  |  | ||||||
|  |  | ||||||
|  | LOG = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CommandManager(cliff.commandmanager.CommandManager): | ||||||
|  |     """Alters Cliff's default CommandManager behaviour to load additiona | ||||||
|  |        command groups after initialization. | ||||||
|  |     """ | ||||||
|  |     def _load_commands(self, group=None): | ||||||
|  |         if not group: | ||||||
|  |             group = self.namespace | ||||||
|  |         for ep in pkg_resources.iter_entry_points(group): | ||||||
|  |             LOG.debug('found command %r' % ep.name) | ||||||
|  |             self.commands[ep.name.replace('_', ' ')] = ep | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     def add_command_group(self, group=None): | ||||||
|  |         """Adds another group of command entrypoints""" | ||||||
|  |         if group: | ||||||
|  |             self._load_commands(group) | ||||||
| @@ -13,7 +13,7 @@ | |||||||
| #   under the License. | #   under the License. | ||||||
| # | # | ||||||
|  |  | ||||||
| """User action implementations""" | """Identity v2.0 User action implementations""" | ||||||
|  |  | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
| @@ -126,7 +126,7 @@ class ListUser(lister.Lister): | |||||||
|     def take_action(self, parsed_args): |     def take_action(self, parsed_args): | ||||||
|         self.log.debug('take_action(%s)' % parsed_args) |         self.log.debug('take_action(%s)' % parsed_args) | ||||||
|         if parsed_args.long: |         if parsed_args.long: | ||||||
|             columns = ('ID', 'Name', 'TenantId', 'Email', 'Enabled') |             columns = ('ID', 'Name', 'Tenant Id', 'Email', 'Enabled') | ||||||
|         else: |         else: | ||||||
|             columns = ('ID', 'Name') |             columns = ('ID', 'Name') | ||||||
|         data = self.app.client_manager.identity.users.list() |         data = self.app.client_manager.identity.users.list() | ||||||
|   | |||||||
							
								
								
									
										247
									
								
								openstackclient/identity/v3/user.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								openstackclient/identity/v3/user.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,247 @@ | |||||||
|  | #   Copyright 2012-2013 OpenStack, LLC. | ||||||
|  | # | ||||||
|  | #   Licensed under the Apache License, Version 2.0 (the "License"); you may | ||||||
|  | #   not use this file except in compliance with the License. You may obtain | ||||||
|  | #   a copy of the License at | ||||||
|  | # | ||||||
|  | #        http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | #   Unless required by applicable law or agreed to in writing, software | ||||||
|  | #   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||||
|  | #   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||||
|  | #   License for the specific language governing permissions and limitations | ||||||
|  | #   under the License. | ||||||
|  | # | ||||||
|  |  | ||||||
|  | """Identity v3 User action implementations""" | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from cliff import command | ||||||
|  | from cliff import lister | ||||||
|  | from cliff import show | ||||||
|  |  | ||||||
|  | from openstackclient.common import utils | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CreateUser(show.ShowOne): | ||||||
|  |     """Create user command""" | ||||||
|  |  | ||||||
|  |     api = 'identity' | ||||||
|  |     log = logging.getLogger(__name__ + '.CreateUser') | ||||||
|  |  | ||||||
|  |     def get_parser(self, prog_name): | ||||||
|  |         parser = super(CreateUser, self).get_parser(prog_name) | ||||||
|  |         parser.add_argument( | ||||||
|  |             'name', | ||||||
|  |             metavar='<user-name>', | ||||||
|  |             help='New user name', | ||||||
|  |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--password', | ||||||
|  |             metavar='<user-password>', | ||||||
|  |             help='New user password', | ||||||
|  |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--email', | ||||||
|  |             metavar='<user-email>', | ||||||
|  |             help='New user email address', | ||||||
|  |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--project', | ||||||
|  |             metavar='<project>', | ||||||
|  |             help='New default project name or ID', | ||||||
|  |         ) | ||||||
|  |         enable_group = parser.add_mutually_exclusive_group() | ||||||
|  |         enable_group.add_argument( | ||||||
|  |             '--enable', | ||||||
|  |             dest='enabled', | ||||||
|  |             action='store_true', | ||||||
|  |             default=True, | ||||||
|  |             help='Enable user', | ||||||
|  |         ) | ||||||
|  |         enable_group.add_argument( | ||||||
|  |             '--disable', | ||||||
|  |             dest='enabled', | ||||||
|  |             action='store_false', | ||||||
|  |             help='Disable user', | ||||||
|  |         ) | ||||||
|  |         return parser | ||||||
|  |  | ||||||
|  |     def take_action(self, parsed_args): | ||||||
|  |         self.log.debug('take_action(%s)' % parsed_args) | ||||||
|  |         identity_client = self.app.client_manager.identity | ||||||
|  |         if parsed_args.project: | ||||||
|  |             project_id = utils.find_resource( | ||||||
|  |                 identity_client.projects, parsed_args.project).id | ||||||
|  |         else: | ||||||
|  |             project_id = None | ||||||
|  |         user = identity_client.users.create( | ||||||
|  |             parsed_args.name, | ||||||
|  |             parsed_args.password, | ||||||
|  |             parsed_args.email, | ||||||
|  |             project_id=project_id, | ||||||
|  |             enabled=parsed_args.enabled, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         info = {} | ||||||
|  |         info.update(user._info) | ||||||
|  |         return zip(*sorted(info.iteritems())) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class DeleteUser(command.Command): | ||||||
|  |     """Delete user command""" | ||||||
|  |  | ||||||
|  |     api = 'identity' | ||||||
|  |     log = logging.getLogger(__name__ + '.DeleteUser') | ||||||
|  |  | ||||||
|  |     def get_parser(self, prog_name): | ||||||
|  |         parser = super(DeleteUser, self).get_parser(prog_name) | ||||||
|  |         parser.add_argument( | ||||||
|  |             'user', | ||||||
|  |             metavar='<user>', | ||||||
|  |             help='Name or ID of user to delete', | ||||||
|  |         ) | ||||||
|  |         return parser | ||||||
|  |  | ||||||
|  |     def take_action(self, parsed_args): | ||||||
|  |         self.log.debug('take_action(%s)' % parsed_args) | ||||||
|  |         identity_client = self.app.client_manager.identity | ||||||
|  |         user = utils.find_resource( | ||||||
|  |             identity_client.users, parsed_args.user) | ||||||
|  |         identity_client.users.delete(user.id) | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ListUser(lister.Lister): | ||||||
|  |     """List user command""" | ||||||
|  |  | ||||||
|  |     api = 'identity' | ||||||
|  |     log = logging.getLogger(__name__ + '.ListUser') | ||||||
|  |  | ||||||
|  |     def get_parser(self, prog_name): | ||||||
|  |         parser = super(ListUser, self).get_parser(prog_name) | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--project', | ||||||
|  |             metavar='<project>', | ||||||
|  |             help='Name or ID of project to filter users', | ||||||
|  |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--long', | ||||||
|  |             action='store_true', | ||||||
|  |             default=False, | ||||||
|  |             help='Additional fields are listed in output', | ||||||
|  |         ) | ||||||
|  |         return parser | ||||||
|  |  | ||||||
|  |     def take_action(self, parsed_args): | ||||||
|  |         self.log.debug('take_action(%s)' % parsed_args) | ||||||
|  |         if parsed_args.long: | ||||||
|  |             columns = ('ID', 'Name', 'Project Id', 'Email', 'Enabled') | ||||||
|  |         else: | ||||||
|  |             columns = ('ID', 'Name') | ||||||
|  |         data = self.app.client_manager.identity.users.list() | ||||||
|  |         return (columns, | ||||||
|  |                 (utils.get_item_properties( | ||||||
|  |                     s, columns, | ||||||
|  |                     formatters={}, | ||||||
|  |                 ) for s in data)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SetUser(command.Command): | ||||||
|  |     """Set user command""" | ||||||
|  |  | ||||||
|  |     api = 'identity' | ||||||
|  |     log = logging.getLogger(__name__ + '.SetUser') | ||||||
|  |  | ||||||
|  |     def get_parser(self, prog_name): | ||||||
|  |         parser = super(SetUser, self).get_parser(prog_name) | ||||||
|  |         parser.add_argument( | ||||||
|  |             'user', | ||||||
|  |             metavar='<user>', | ||||||
|  |             help='Name or ID of user to change', | ||||||
|  |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--name', | ||||||
|  |             metavar='<new-user-name>', | ||||||
|  |             help='New user name', | ||||||
|  |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--password', | ||||||
|  |             metavar='<user-password>', | ||||||
|  |             help='New user password', | ||||||
|  |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--email', | ||||||
|  |             metavar='<user-email>', | ||||||
|  |             help='New user email address', | ||||||
|  |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             '--project', | ||||||
|  |             metavar='<project>', | ||||||
|  |             help='New default project name or ID', | ||||||
|  |         ) | ||||||
|  |         enable_group = parser.add_mutually_exclusive_group() | ||||||
|  |         enable_group.add_argument( | ||||||
|  |             '--enable', | ||||||
|  |             dest='enabled', | ||||||
|  |             action='store_true', | ||||||
|  |             default=True, | ||||||
|  |             help='Enable user (default)', | ||||||
|  |         ) | ||||||
|  |         enable_group.add_argument( | ||||||
|  |             '--disable', | ||||||
|  |             dest='enabled', | ||||||
|  |             action='store_false', | ||||||
|  |             help='Disable user', | ||||||
|  |         ) | ||||||
|  |         return parser | ||||||
|  |  | ||||||
|  |     def take_action(self, parsed_args): | ||||||
|  |         self.log.debug('take_action(%s)' % parsed_args) | ||||||
|  |         identity_client = self.app.client_manager.identity | ||||||
|  |         user = utils.find_resource( | ||||||
|  |             identity_client.users, parsed_args.user) | ||||||
|  |         kwargs = {} | ||||||
|  |         if parsed_args.name: | ||||||
|  |             kwargs['name'] = parsed_args.name | ||||||
|  |         if parsed_args.email: | ||||||
|  |             kwargs['email'] = parsed_args.email | ||||||
|  |         if parsed_args.project: | ||||||
|  |             project_id = utils.find_resource( | ||||||
|  |                 identity_client.projects, parsed_args.project).id | ||||||
|  |             kwargs['projectId'] = project_id | ||||||
|  |         if 'enabled' in parsed_args: | ||||||
|  |             kwargs['enabled'] = parsed_args.enabled | ||||||
|  |  | ||||||
|  |         if not len(kwargs): | ||||||
|  |             stdout.write("User not updated, no arguments present") | ||||||
|  |             return | ||||||
|  |         identity_client.users.update(user.id, **kwargs) | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ShowUser(show.ShowOne): | ||||||
|  |     """Show user command""" | ||||||
|  |  | ||||||
|  |     api = 'identity' | ||||||
|  |     log = logging.getLogger(__name__ + '.ShowUser') | ||||||
|  |  | ||||||
|  |     def get_parser(self, prog_name): | ||||||
|  |         parser = super(ShowUser, self).get_parser(prog_name) | ||||||
|  |         parser.add_argument( | ||||||
|  |             'user', | ||||||
|  |             metavar='<user>', | ||||||
|  |             help='Name or ID of user to display', | ||||||
|  |         ) | ||||||
|  |         return parser | ||||||
|  |  | ||||||
|  |     def take_action(self, parsed_args): | ||||||
|  |         self.log.debug('take_action(%s)' % parsed_args) | ||||||
|  |         identity_client = self.app.client_manager.identity | ||||||
|  |         user = utils.find_resource( | ||||||
|  |             identity_client.users, parsed_args.user) | ||||||
|  |  | ||||||
|  |         info = {} | ||||||
|  |         info.update(user._info) | ||||||
|  |         return zip(*sorted(info.iteritems())) | ||||||
| @@ -21,17 +21,22 @@ import os | |||||||
| import sys | import sys | ||||||
|  |  | ||||||
| from cliff.app import App | from cliff.app import App | ||||||
| from cliff.commandmanager import CommandManager | from cliff.help import HelpAction | ||||||
|  |  | ||||||
| from openstackclient.common import clientmanager | from openstackclient.common import clientmanager | ||||||
| from openstackclient.common import exceptions as exc | from openstackclient.common import exceptions as exc | ||||||
| from openstackclient.common import openstackkeyring | from openstackclient.common import openstackkeyring | ||||||
| from openstackclient.common import utils | from openstackclient.common import utils | ||||||
|  | from openstackclient.common.commandmanager import CommandManager | ||||||
|  |  | ||||||
|  |  | ||||||
| VERSION = '0.1' | VERSION = '0.1' | ||||||
| KEYRING_SERVICE = 'openstack' | KEYRING_SERVICE = 'openstack' | ||||||
|  |  | ||||||
|  | DEFAULT_COMPUTE_API_VERSION = '2' | ||||||
|  | DEFAULT_IDENTITY_API_VERSION = '2.0' | ||||||
|  | DEFAULT_IMAGE_API_VERSION = '1.0' | ||||||
|  |  | ||||||
|  |  | ||||||
| def env(*vars, **kwargs): | def env(*vars, **kwargs): | ||||||
|     """Search for the first defined of possibly many env vars |     """Search for the first defined of possibly many env vars | ||||||
| @@ -63,6 +68,35 @@ class OpenStackShell(App): | |||||||
|         # password flow auth |         # password flow auth | ||||||
|         self.auth_client = None |         self.auth_client = None | ||||||
|  |  | ||||||
|  |         # NOTE(dtroyer): This hack changes the help action that Cliff | ||||||
|  |         #                automatically adds to the parser so we can defer | ||||||
|  |         #                its execution until after the api-versioned commands | ||||||
|  |         #                have been loaded.  There doesn't seem to be a | ||||||
|  |         #                way to edit/remove anything from an existing parser. | ||||||
|  |  | ||||||
|  |         # Replace the cliff-added HelpAction to defer its execution | ||||||
|  |         self.DeferredHelpAction = None | ||||||
|  |         for a in self.parser._actions: | ||||||
|  |             if type(a) == HelpAction: | ||||||
|  |                 # Found it, save and replace it | ||||||
|  |                 self.DeferredHelpAction = a | ||||||
|  |  | ||||||
|  |                 # These steps are argparse-implementation-dependent | ||||||
|  |                 self.parser._actions.remove(a) | ||||||
|  |                 if self.parser._option_string_actions['-h']: | ||||||
|  |                     del self.parser._option_string_actions['-h'] | ||||||
|  |                 if self.parser._option_string_actions['--help']: | ||||||
|  |                     del self.parser._option_string_actions['--help'] | ||||||
|  |  | ||||||
|  |                 # Make a new help option to just set a flag | ||||||
|  |                 self.parser.add_argument( | ||||||
|  |                     '-h', '--help', | ||||||
|  |                     action='store_true', | ||||||
|  |                     dest='deferred_help', | ||||||
|  |                     default=False, | ||||||
|  |                     help="show this help message and exit", | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|     def build_option_parser(self, description, version): |     def build_option_parser(self, description, version): | ||||||
|         parser = super(OpenStackShell, self).build_option_parser( |         parser = super(OpenStackShell, self).build_option_parser( | ||||||
|             description, |             description, | ||||||
| @@ -102,20 +136,30 @@ class OpenStackShell(App): | |||||||
|         parser.add_argument( |         parser.add_argument( | ||||||
|             '--os-identity-api-version', |             '--os-identity-api-version', | ||||||
|             metavar='<identity-api-version>', |             metavar='<identity-api-version>', | ||||||
|             default=env('OS_IDENTITY_API_VERSION', default='2.0'), |             default=env( | ||||||
|             help='Identity API version, default=2.0 ' |                 'OS_IDENTITY_API_VERSION', | ||||||
|                  '(Env: OS_IDENTITY_API_VERSION)') |                 default=DEFAULT_IDENTITY_API_VERSION), | ||||||
|  |             help='Identity API version, default=' + | ||||||
|  |                  DEFAULT_IDENTITY_API_VERSION + | ||||||
|  |                  ' (Env: OS_IDENTITY_API_VERSION)') | ||||||
|         parser.add_argument( |         parser.add_argument( | ||||||
|             '--os-compute-api-version', |             '--os-compute-api-version', | ||||||
|             metavar='<compute-api-version>', |             metavar='<compute-api-version>', | ||||||
|             default=env('OS_COMPUTE_API_VERSION', default='2'), |             default=env( | ||||||
|             help='Compute API version, default=2 ' |                 'OS_COMPUTE_API_VERSION', | ||||||
|                  '(Env: OS_COMPUTE_API_VERSION)') |                 default=DEFAULT_COMPUTE_API_VERSION), | ||||||
|  |             help='Compute API version, default=' + | ||||||
|  |                  DEFAULT_COMPUTE_API_VERSION + | ||||||
|  |                  ' (Env: OS_COMPUTE_API_VERSION)') | ||||||
|         parser.add_argument( |         parser.add_argument( | ||||||
|             '--os-image-api-version', |             '--os-image-api-version', | ||||||
|             metavar='<image-api-version>', |             metavar='<image-api-version>', | ||||||
|             default=env('OS_IMAGE_API_VERSION', default='1.0'), |             default=env( | ||||||
|             help='Image API version, default=1.0 (Env: OS_IMAGE_API_VERSION)') |                 'OS_IMAGE_API_VERSION', | ||||||
|  |                 default=DEFAULT_IMAGE_API_VERSION), | ||||||
|  |             help='Image API version, default=' + | ||||||
|  |                  DEFAULT_IMAGE_API_VERSION + | ||||||
|  |                  ' (Env: OS_IMAGE_API_VERSION)') | ||||||
|         parser.add_argument( |         parser.add_argument( | ||||||
|             '--os-token', |             '--os-token', | ||||||
|             metavar='<token>', |             metavar='<token>', | ||||||
| @@ -251,6 +295,16 @@ class OpenStackShell(App): | |||||||
|             'image': self.options.os_image_api_version, |             'image': self.options.os_image_api_version, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         # Add the API version-specific commands | ||||||
|  |         for api in self.api_version.keys(): | ||||||
|  |             version = '.v' + self.api_version[api].replace('.', '_') | ||||||
|  |             self.command_manager.add_command_group( | ||||||
|  |                 'openstack.' + api + version) | ||||||
|  |  | ||||||
|  |         # Handle deferred help and exit | ||||||
|  |         if self.options.deferred_help: | ||||||
|  |             self.DeferredHelpAction(self.parser, self.parser, None, None) | ||||||
|  |  | ||||||
|         # If the user is not asking for help, make sure they |         # If the user is not asking for help, make sure they | ||||||
|         # have given us auth. |         # have given us auth. | ||||||
|         cmd_name = None |         cmd_name = None | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								setup.py
									
									
									
									
									
								
							| @@ -55,6 +55,8 @@ setuptools.setup( | |||||||
|     entry_points={ |     entry_points={ | ||||||
|         'console_scripts': ['openstack=openstackclient.shell:main'], |         'console_scripts': ['openstack=openstackclient.shell:main'], | ||||||
|         'openstack.cli': [ |         'openstack.cli': [ | ||||||
|  |         ], | ||||||
|  |         'openstack.identity.v2_0': [ | ||||||
|             'create_endpoint=' + |             'create_endpoint=' + | ||||||
|             'openstackclient.identity.v2_0.endpoint:CreateEndpoint', |             'openstackclient.identity.v2_0.endpoint:CreateEndpoint', | ||||||
|             'delete_endpoint=' + |             'delete_endpoint=' + | ||||||
| @@ -73,16 +75,6 @@ setuptools.setup( | |||||||
|             'remove_role=' + |             'remove_role=' + | ||||||
|             'openstackclient.identity.v2_0.role:RemoveRole', |             'openstackclient.identity.v2_0.role:RemoveRole', | ||||||
|             'show_role=openstackclient.identity.v2_0.role:ShowRole', |             'show_role=openstackclient.identity.v2_0.role:ShowRole', | ||||||
|             'create_server=openstackclient.compute.v2.server:CreateServer', |  | ||||||
|             'delete_server=openstackclient.compute.v2.server:DeleteServer', |  | ||||||
|             'list_server=openstackclient.compute.v2.server:ListServer', |  | ||||||
|             'pause_server=openstackclient.compute.v2.server:PauseServer', |  | ||||||
|             'reboot_server=openstackclient.compute.v2.server:RebootServer', |  | ||||||
|             'rebuild_server=openstackclient.compute.v2.server:RebuildServer', |  | ||||||
|             'resume_server=openstackclient.compute.v2.server:ResumeServer', |  | ||||||
|             'show_server=openstackclient.compute.v2.server:ShowServer', |  | ||||||
|             'suspend_server=openstackclient.compute.v2.server:SuspendServer', |  | ||||||
|             'unpause_server=openstackclient.compute.v2.server:UnpauseServer', |  | ||||||
|             'create_service=' + |             'create_service=' + | ||||||
|             'openstackclient.identity.v2_0.service:CreateService', |             'openstackclient.identity.v2_0.service:CreateService', | ||||||
|             'delete_service=' + |             'delete_service=' + | ||||||
| @@ -96,6 +88,7 @@ setuptools.setup( | |||||||
|             'list_tenant=openstackclient.identity.v2_0.tenant:ListTenant', |             'list_tenant=openstackclient.identity.v2_0.tenant:ListTenant', | ||||||
|             'set_tenant=openstackclient.identity.v2_0.tenant:SetTenant', |             'set_tenant=openstackclient.identity.v2_0.tenant:SetTenant', | ||||||
|             'show_tenant=openstackclient.identity.v2_0.tenant:ShowTenant', |             'show_tenant=openstackclient.identity.v2_0.tenant:ShowTenant', | ||||||
|  |             'list_user-role=openstackclient.identity.v2_0.role:ListUserRole', | ||||||
|             'create_user=' + |             'create_user=' + | ||||||
|             'openstackclient.identity.v2_0.user:CreateUser', |             'openstackclient.identity.v2_0.user:CreateUser', | ||||||
|             'delete_user=' + |             'delete_user=' + | ||||||
| @@ -103,10 +96,8 @@ setuptools.setup( | |||||||
|             'list_user=openstackclient.identity.v2_0.user:ListUser', |             'list_user=openstackclient.identity.v2_0.user:ListUser', | ||||||
|             'set_user=openstackclient.identity.v2_0.user:SetUser', |             'set_user=openstackclient.identity.v2_0.user:SetUser', | ||||||
|             'show_user=openstackclient.identity.v2_0.user:ShowUser', |             'show_user=openstackclient.identity.v2_0.user:ShowUser', | ||||||
|             'list_user-role=openstackclient.identity.v2_0.role:ListUserRole', |         ], | ||||||
|             'list_image=openstackclient.image.v2.image:ListImage', |         'openstack.identity.v3': [ | ||||||
|             'show_image=openstackclient.image.v2.image:ShowImage', |  | ||||||
|             'save_image=openstackclient.image.v2.image:SaveImage', |  | ||||||
|             'create_group=openstackclient.identity.v3.group:CreateGroup', |             'create_group=openstackclient.identity.v3.group:CreateGroup', | ||||||
|             'delete_group=openstackclient.identity.v3.group:DeleteGroup', |             'delete_group=openstackclient.identity.v3.group:DeleteGroup', | ||||||
|             'set_group=openstackclient.identity.v3.group:SetGroup', |             'set_group=openstackclient.identity.v3.group:SetGroup', | ||||||
| @@ -119,6 +110,30 @@ setuptools.setup( | |||||||
|             'set_project=openstackclient.identity.v3.project:SetProject', |             'set_project=openstackclient.identity.v3.project:SetProject', | ||||||
|             'show_project=openstackclient.identity.v3.project:ShowProject', |             'show_project=openstackclient.identity.v3.project:ShowProject', | ||||||
|             'list_project=openstackclient.identity.v3.project:ListProject', |             'list_project=openstackclient.identity.v3.project:ListProject', | ||||||
|         ] |             'create_user=' + | ||||||
|  |             'openstackclient.identity.v3.user:CreateUser', | ||||||
|  |             'delete_user=' + | ||||||
|  |             'openstackclient.identity.v3.user:DeleteUser', | ||||||
|  |             'list_user=openstackclient.identity.v3.user:ListUser', | ||||||
|  |             'set_user=openstackclient.identity.v3.user:SetUser', | ||||||
|  |             'show_user=openstackclient.identity.v3.user:ShowUser', | ||||||
|  |         ], | ||||||
|  |         'openstack.image.v2': [ | ||||||
|  |             'list_image=openstackclient.image.v2.image:ListImage', | ||||||
|  |             'show_image=openstackclient.image.v2.image:ShowImage', | ||||||
|  |             'save_image=openstackclient.image.v2.image:SaveImage', | ||||||
|  |         ], | ||||||
|  |         'openstack.compute.v2': [ | ||||||
|  |             'create_server=openstackclient.compute.v2.server:CreateServer', | ||||||
|  |             'delete_server=openstackclient.compute.v2.server:DeleteServer', | ||||||
|  |             'list_server=openstackclient.compute.v2.server:ListServer', | ||||||
|  |             'pause_server=openstackclient.compute.v2.server:PauseServer', | ||||||
|  |             'reboot_server=openstackclient.compute.v2.server:RebootServer', | ||||||
|  |             'rebuild_server=openstackclient.compute.v2.server:RebuildServer', | ||||||
|  |             'resume_server=openstackclient.compute.v2.server:ResumeServer', | ||||||
|  |             'show_server=openstackclient.compute.v2.server:ShowServer', | ||||||
|  |             'suspend_server=openstackclient.compute.v2.server:SuspendServer', | ||||||
|  |             'unpause_server=openstackclient.compute.v2.server:UnpauseServer', | ||||||
|  |         ], | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										71
									
								
								tests/common/test_commandmanager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								tests/common/test_commandmanager.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | #   Copyright 2012-2013 OpenStack, LLC. | ||||||
|  | # | ||||||
|  | #   Licensed under the Apache License, Version 2.0 (the "License"); you may | ||||||
|  | #   not use this file except in compliance with the License. You may obtain | ||||||
|  | #   a copy of the License at | ||||||
|  | # | ||||||
|  | #        http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | #   Unless required by applicable law or agreed to in writing, software | ||||||
|  | #   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||||
|  | #   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||||
|  | #   License for the specific language governing permissions and limitations | ||||||
|  | #   under the License. | ||||||
|  | # | ||||||
|  |  | ||||||
|  | import mock | ||||||
|  |  | ||||||
|  | from openstackclient.common import commandmanager | ||||||
|  | from tests import utils | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FakeCommand(object): | ||||||
|  |     @classmethod | ||||||
|  |     def load(cls): | ||||||
|  |         return cls | ||||||
|  |  | ||||||
|  |     def __init__(self): | ||||||
|  |         return | ||||||
|  |  | ||||||
|  | FAKE_CMD_ONE = FakeCommand | ||||||
|  | FAKE_CMD_TWO = FakeCommand | ||||||
|  | FAKE_CMD_ALPHA = FakeCommand | ||||||
|  | FAKE_CMD_BETA = FakeCommand | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FakeCommandManager(commandmanager.CommandManager): | ||||||
|  |     commands = {} | ||||||
|  |  | ||||||
|  |     def _load_commands(self, group=None): | ||||||
|  |         if not group: | ||||||
|  |             self.commands['one'] = FAKE_CMD_ONE | ||||||
|  |             self.commands['two'] = FAKE_CMD_TWO | ||||||
|  |         else: | ||||||
|  |             self.commands['alpha'] = FAKE_CMD_ALPHA | ||||||
|  |             self.commands['beta'] = FAKE_CMD_BETA | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestCommandManager(utils.TestCase): | ||||||
|  |     def test_add_command_group(self): | ||||||
|  |         mgr = FakeCommandManager('test') | ||||||
|  |  | ||||||
|  |         # Make sure add_command() still functions | ||||||
|  |         mock_cmd_one = mock.Mock() | ||||||
|  |         mgr.add_command('mock', mock_cmd_one) | ||||||
|  |         cmd_mock, name, args = mgr.find_command(['mock']) | ||||||
|  |         self.assertEqual(cmd_mock, mock_cmd_one) | ||||||
|  |  | ||||||
|  |         # Find a command added in initialization | ||||||
|  |         cmd_one, name, args = mgr.find_command(['one']) | ||||||
|  |         self.assertEqual(cmd_one, FAKE_CMD_ONE) | ||||||
|  |  | ||||||
|  |         # Load another command group | ||||||
|  |         mgr.add_command_group('latin') | ||||||
|  |  | ||||||
|  |         # Find a new command | ||||||
|  |         cmd_alpha, name, args = mgr.find_command(['alpha']) | ||||||
|  |         self.assertEqual(cmd_alpha, FAKE_CMD_ALPHA) | ||||||
|  |  | ||||||
|  |         # Ensure that the original commands were not overwritten | ||||||
|  |         cmd_two, name, args = mgr.find_command(['two']) | ||||||
|  |         self.assertEqual(cmd_two, FAKE_CMD_TWO) | ||||||
| @@ -108,6 +108,30 @@ class TestShell(utils.TestCase): | |||||||
|                              default_args["image_api_version"]) |                              default_args["image_api_version"]) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestShellHelp(TestShell): | ||||||
|  |     """Test the deferred help flag""" | ||||||
|  |     def setUp(self): | ||||||
|  |         super(TestShellHelp, self).setUp() | ||||||
|  |         self.orig_env, os.environ = os.environ, {} | ||||||
|  |  | ||||||
|  |     def tearDown(self): | ||||||
|  |         super(TestShellHelp, self).tearDown() | ||||||
|  |         os.environ = self.orig_env | ||||||
|  |  | ||||||
|  |     def test_help_options(self): | ||||||
|  |         flag = "-h list server" | ||||||
|  |         kwargs = { | ||||||
|  |             "deferred_help": True, | ||||||
|  |         } | ||||||
|  |         with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", | ||||||
|  |                         self.app): | ||||||
|  |             _shell, _cmd = make_shell(), flag | ||||||
|  |             fake_execute(_shell, _cmd) | ||||||
|  |  | ||||||
|  |             self.assertEqual(_shell.options.deferred_help, | ||||||
|  |                              kwargs["deferred_help"]) | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestShellPasswordAuth(TestShell): | class TestShellPasswordAuth(TestShell): | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|         super(TestShellPasswordAuth, self).setUp() |         super(TestShellPasswordAuth, self).setUp() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Dean Troyer
					Dean Troyer