diff options
Diffstat (limited to 'openstackclient/common')
| -rw-r--r-- | openstackclient/common/availability_zone.py | 2 | ||||
| -rw-r--r-- | openstackclient/common/client_config.py | 196 | ||||
| -rw-r--r-- | openstackclient/common/clientmanager.py | 27 | ||||
| -rw-r--r-- | openstackclient/common/configuration.py | 2 | ||||
| -rw-r--r-- | openstackclient/common/extension.py | 2 | ||||
| -rw-r--r-- | openstackclient/common/limits.py | 2 | ||||
| -rw-r--r-- | openstackclient/common/module.py | 52 | ||||
| -rw-r--r-- | openstackclient/common/quota.py | 183 |
8 files changed, 254 insertions, 212 deletions
diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index 63c55370..b2385ef7 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -88,7 +88,7 @@ def _xform_network_availability_zone(az): class ListAvailabilityZone(command.Lister): - """List availability zones and their status""" + _description = _("List availability zones and their status") def get_parser(self, prog_name): parser = super(ListAvailabilityZone, self).get_parser(prog_name) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index 30286df8..a22dd0cb 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -13,122 +13,15 @@ """OpenStackConfig subclass for argument compatibility""" -import logging - -from os_client_config import config -from os_client_config import exceptions as occ_exceptions - - -LOG = logging.getLogger(__name__) +from osc_lib.cli import client_config # Sublcass OpenStackConfig in order to munge config values # before auth plugins are loaded -class OSC_Config(config.OpenStackConfig): - - # TODO(dtroyer): Once os-client-config with pw_func argument is in - # global-requirements we can remove __init()__ - def __init__( - self, - config_files=None, - vendor_files=None, - override_defaults=None, - force_ipv4=None, - envvar_prefix=None, - secure_files=None, - pw_func=None, - ): - ret = super(OSC_Config, self).__init__( - config_files=config_files, - vendor_files=vendor_files, - override_defaults=override_defaults, - force_ipv4=force_ipv4, - envvar_prefix=envvar_prefix, - secure_files=secure_files, - ) - - # NOTE(dtroyer): This will be pushed down into os-client-config - # The default is there is no callback, the calling - # application must specify what to use, typically - # it will be osc_lib.shell.prompt_for_password() - if '_pw_callback' not in vars(self): - # Set the default if it doesn't already exist - self._pw_callback = None - if pw_func is not None: - # Set the passed in value - self._pw_callback = pw_func - - return ret - - def _auth_select_default_plugin(self, config): - """Select a default plugin based on supplied arguments - - Migrated from auth.select_auth_plugin() - """ - - identity_version = config.get('identity_api_version', '') - - if config.get('username', None) and not config.get('auth_type', None): - if identity_version == '3': - config['auth_type'] = 'v3password' - elif identity_version.startswith('2'): - config['auth_type'] = 'v2password' - else: - # let keystoneauth figure it out itself - config['auth_type'] = 'password' - elif config.get('token', None) and not config.get('auth_type', None): - if identity_version == '3': - config['auth_type'] = 'v3token' - elif identity_version.startswith('2'): - config['auth_type'] = 'v2token' - else: - # let keystoneauth figure it out itself - config['auth_type'] = 'token' - else: - # The ultimate default is similar to the original behaviour, - # but this time with version discovery - if not config.get('auth_type', None): - config['auth_type'] = 'password' - - LOG.debug("Auth plugin %s selected" % config['auth_type']) - return config - - def _auth_v2_arguments(self, config): - """Set up v2-required arguments from v3 info - - Migrated from auth.build_auth_params() - """ - - if ('auth_type' in config and config['auth_type'].startswith("v2")): - if 'project_id' in config['auth']: - config['auth']['tenant_id'] = config['auth']['project_id'] - if 'project_name' in config['auth']: - config['auth']['tenant_name'] = config['auth']['project_name'] - return config - - def _auth_v2_ignore_v3(self, config): - """Remove v3 arguemnts if present for v2 plugin - - Migrated from clientmanager.setup_auth() - """ - - # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID - # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then - # ignore all domain related configs. - if (config.get('identity_api_version', '').startswith('2') and - config.get('auth_type', None).endswith('password')): - domain_props = [ - 'project_domain_id', - 'project_domain_name', - 'user_domain_id', - 'user_domain_name', - ] - for prop in domain_props: - if config['auth'].pop(prop, None) is not None: - LOG.warning("Ignoring domain related config " + - prop + " because identity API version is 2.0") - return config +class OSC_Config(client_config.OSC_Config): + # TODO(dtroyer): Remove _auth_default_domain when the v3otp fix is + # backported to osc-lib, should be in release 1.3.0 def _auth_default_domain(self, config): """Set a default domain from available arguments @@ -149,6 +42,7 @@ class OSC_Config(config.OpenStackConfig): # present, then do not change the behaviour. Otherwise, set the # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. if ( + auth_type in ("password", "v3password", "v3totp") and not config['auth'].get('project_domain_id') and not config['auth'].get('project_domain_name') ): @@ -168,21 +62,6 @@ class OSC_Config(config.OpenStackConfig): config['auth']['user_domain_id'] = default_domain return config - def auth_config_hook(self, config): - """Allow examination of config values before loading auth plugin - - OpenStackClient will override this to perform additional chacks - on auth_type. - """ - - config = self._auth_select_default_plugin(config) - config = self._auth_v2_arguments(config) - config = self._auth_v2_ignore_v3(config) - config = self._auth_default_domain(config) - - LOG.debug("auth_config_hook(): %s" % config) - return config - def load_auth_plugin(self, config): """Get auth plugin and validate args""" @@ -190,68 +69,3 @@ class OSC_Config(config.OpenStackConfig): config = self._validate_auth(config, loader) auth_plugin = loader.load_from_options(**config['auth']) return auth_plugin - - def _validate_auth_ksc(self, config, cloud, fixed_argparse=None): - """Old compatibility hack for OSC, no longer needed/wanted""" - return config - - def _validate_auth(self, config, loader, fixed_argparse=None): - """Validate auth plugin arguments""" - # May throw a keystoneauth1.exceptions.NoMatchingPlugin - - plugin_options = loader.get_options() - - msgs = [] - prompt_options = [] - for p_opt in plugin_options: - # if it's in config, win, move it and kill it from config dict - # if it's in config.auth but not in config we're good - # deprecated loses to current - # provided beats default, deprecated or not - winning_value = self._find_winning_auth_value(p_opt, config) - if not winning_value: - winning_value = self._find_winning_auth_value( - p_opt, config['auth']) - - # if the plugin tells us that this value is required - # then error if it's doesn't exist now - if not winning_value and p_opt.required: - msgs.append( - 'Missing value {auth_key}' - ' required for auth plugin {plugin}'.format( - auth_key=p_opt.name, plugin=config.get('auth_type'), - ) - ) - - # Clean up after ourselves - for opt in [p_opt.name] + [o.name for o in p_opt.deprecated]: - opt = opt.replace('-', '_') - config.pop(opt, None) - config['auth'].pop(opt, None) - - if winning_value: - # Prefer the plugin configuration dest value if the value's key - # is marked as depreciated. - if p_opt.dest is None: - config['auth'][p_opt.name.replace('-', '_')] = ( - winning_value) - else: - config['auth'][p_opt.dest] = winning_value - - # See if this needs a prompting - if ( - 'prompt' in vars(p_opt) and - p_opt.prompt is not None and - p_opt.dest not in config['auth'] and - self._pw_callback is not None - ): - # Defer these until we know all required opts are present - prompt_options.append(p_opt) - - if msgs: - raise occ_exceptions.OpenStackConfigException('\n'.join(msgs)) - else: - for p_opt in prompt_options: - config['auth'][p_opt.dest] = self._pw_callback(p_opt.prompt) - - return config diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 23c35a3b..3e1a50e3 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -59,6 +59,8 @@ class ClientManager(clientmanager.ClientManager): self._interface = self.interface self._cacert = self.cacert self._insecure = not self.verify + # store original auth_type + self._original_auth_type = cli_options.auth_type def setup_auth(self): """Set up authentication""" @@ -73,12 +75,33 @@ class ClientManager(clientmanager.ClientManager): if self._cli_options._openstack_config is not None: self._cli_options._openstack_config._pw_callback = \ shell.prompt_for_password + try: + self._cli_options._auth = \ + self._cli_options._openstack_config.load_auth_plugin( + self._cli_options.config, + ) + except TypeError as e: + self._fallback_load_auth_plugin(e) + + return super(ClientManager, self).setup_auth() + + def _fallback_load_auth_plugin(self, e): + # NOTES(RuiChen): Hack to avoid auth plugins choking on data they don't + # expect, delete fake token and endpoint, then try to + # load auth plugin again with user specified options. + # We know it looks ugly, but it's necessary. + if self._cli_options.config['auth']['token'] == 'x': + # restore original auth_type + self._cli_options.config['auth_type'] = \ + self._original_auth_type + del self._cli_options.config['auth']['token'] + del self._cli_options.config['auth']['endpoint'] self._cli_options._auth = \ self._cli_options._openstack_config.load_auth_plugin( self._cli_options.config, ) - - return super(ClientManager, self).setup_auth() + else: + raise e def is_network_endpoint_enabled(self): """Check if the network endpoint is enabled""" diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index 016e9191..57825bb0 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -23,7 +23,7 @@ REDACTED = "<redacted>" class ShowConfiguration(command.ShowOne): - """Display configuration details""" + _description = _("Display configuration details") def get_parser(self, prog_name): parser = super(ShowConfiguration, self).get_parser(prog_name) diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index 07c407f6..d5b72238 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__) class ListExtension(command.Lister): - """List API extensions""" + _description = _("List API extensions") def get_parser(self, prog_name): parser = super(ListExtension, self).get_parser(prog_name) diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index f7aa82f6..957f1d02 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -25,7 +25,7 @@ from openstackclient.identity import common as identity_common class ShowLimits(command.Lister): - """Show compute and block storage limits""" + _description = _("Show compute and block storage limits") def get_parser(self, prog_name): parser = super(ShowLimits, self).get_parser(prog_name) diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 7c5fcd55..20497f21 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -25,16 +25,30 @@ from openstackclient.i18n import _ class ListCommand(command.Lister): - """List recognized commands by group""" + _description = _("List recognized commands by group") auth_required = False + def get_parser(self, prog_name): + parser = super(ListCommand, self).get_parser(prog_name) + parser.add_argument( + '--group', + metavar='<group-keyword>', + help=_('Show commands filtered by a command group, for example: ' + 'identity, volume, compute, image, network and ' + 'other keywords'), + ) + return parser + def take_action(self, parsed_args): cm = self.app.command_manager groups = cm.get_command_groups() groups = sorted(groups) columns = ('Command Group', 'Commands') + if parsed_args.group: + groups = (group for group in groups if parsed_args.group in group) + commands = [] for group in groups: command_names = cm.get_command_names(group) @@ -53,7 +67,7 @@ class ListCommand(command.Lister): class ListModule(command.ShowOne): - """List module versions""" + _description = _("List module versions") auth_required = False @@ -74,15 +88,29 @@ class ListModule(command.ShowOne): mods = sys.modules for k in mods.keys(): k = k.split('.')[0] - # TODO(dtroyer): Need a better way to decide which modules to - # show for the default (not --all) invocation. - # It should be just the things we actually care - # about like client and plugin modules... - if (parsed_args.all or 'client' in k): - try: - data[k] = mods[k].__version__ - except AttributeError: - # aw, just skip it - pass + # Skip private modules and the modules that had been added, + # like: keystoneclient, keystoneclient.exceptions and + # keystoneclient.auth + if not k.startswith('_') and k not in data: + # TODO(dtroyer): Need a better way to decide which modules to + # show for the default (not --all) invocation. + # It should be just the things we actually care + # about like client and plugin modules... + if (parsed_args.all or + # Handle xxxclient and openstacksdk + (k.endswith('client') or k == 'openstack')): + try: + # NOTE(RuiChen): openstacksdk bug/1588823 exist, + # no good way to add __version__ for + # openstack module properly, hard code + # looks bad, but openstacksdk module + # information is important. + if k == 'openstack': + data[k] = mods[k].version.__version__ + else: + data[k] = mods[k].__version__ + except Exception: + # Catch all exceptions, just skip it + pass return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 8f099cc9..ec4c8b51 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -16,6 +16,7 @@ """Quota action implementations""" import itertools +import logging import sys from osc_lib.command import command @@ -25,6 +26,8 @@ import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + # List the quota items, map the internal argument name to the option # name that the user sees. @@ -78,9 +81,179 @@ NETWORK_QUOTAS = { 'l7policy': 'l7policies', } +NETWORK_KEYS = ['floating_ips', 'networks', 'rbac_policies', 'routers', + 'ports', 'security_group_rules', 'security_groups', + 'subnet_pools', 'subnets'] + + +def _xform_get_quota(data, value, keys): + res = [] + res_info = {} + for key in keys: + res_info[key] = getattr(data, key, '') + + res_info['id'] = value + res.append(res_info) + return res + + +class ListQuota(command.Lister): + _description = _("List quotas for all projects " + "with non-default quota values") + + def get_parser(self, prog_name): + parser = super(ListQuota, self).get_parser(prog_name) + option = parser.add_mutually_exclusive_group(required=True) + option.add_argument( + '--compute', + action='store_true', + default=False, + help=_('List compute quota'), + ) + option.add_argument( + '--volume', + action='store_true', + default=False, + help=_('List volume quota'), + ) + option.add_argument( + '--network', + action='store_true', + default=False, + help=_('List network quota'), + ) + return parser + + def take_action(self, parsed_args): + projects = self.app.client_manager.identity.projects.list() + result = [] + project_ids = [getattr(p, 'id', '') for p in projects] + + if parsed_args.compute: + compute_client = self.app.client_manager.compute + for p in project_ids: + data = compute_client.quotas.get(p) + result_data = _xform_get_quota(data, p, + COMPUTE_QUOTAS.keys()) + default_data = compute_client.quotas.defaults(p) + result_default = _xform_get_quota(default_data, + p, + COMPUTE_QUOTAS.keys()) + if result_default != result_data: + result += result_data + + columns = ( + 'id', + 'cores', + 'fixed_ips', + 'injected_files', + 'injected_file_content_bytes', + 'injected_file_path_bytes', + 'instances', + 'key_pairs', + 'metadata_items', + 'ram', + 'server_groups', + 'server_group_members', + ) + column_headers = ( + 'Project ID', + 'Cores', + 'Fixed IPs', + 'Injected Files', + 'Injected File Content Bytes', + 'Injected File Path Bytes', + 'Instances', + 'Key Pairs', + 'Metadata Items', + 'Ram', + 'Server Groups', + 'Server Group Members', + ) + return (column_headers, + (utils.get_dict_properties( + s, columns, + ) for s in result)) + if parsed_args.volume: + volume_client = self.app.client_manager.volume + for p in project_ids: + data = volume_client.quotas.get(p) + result_data = _xform_get_quota(data, p, + VOLUME_QUOTAS.keys()) + default_data = volume_client.quotas.defaults(p) + result_default = _xform_get_quota(default_data, + p, + VOLUME_QUOTAS.keys()) + if result_default != result_data: + result += result_data + + columns = ( + 'id', + 'backups', + 'backup_gigabytes', + 'gigabytes', + 'per_volume_gigabytes', + 'snapshots', + 'volumes', + ) + column_headers = ( + 'Project ID', + 'Backups', + 'Backup Gigabytes', + 'Gigabytes', + 'Per Volume Gigabytes', + 'Snapshots', + 'Volumes', + ) + return (column_headers, + (utils.get_dict_properties( + s, columns, + ) for s in result)) + if parsed_args.network: + client = self.app.client_manager.network + for p in project_ids: + data = client.get_quota(p) + result_data = _xform_get_quota(data, p, NETWORK_KEYS) + default_data = client.get_quota_default(p) + result_default = _xform_get_quota(default_data, + p, NETWORK_KEYS) + if result_default != result_data: + result += result_data + + columns = ( + 'id', + 'floating_ips', + 'networks', + 'ports', + 'rbac_policies', + 'routers', + 'security_groups', + 'security_group_rules', + 'subnets', + 'subnet_pools', + ) + column_headers = ( + 'Project ID', + 'Floating IPs', + 'Networks', + 'Ports', + 'RBAC Policies', + 'Routers', + 'Security Groups', + 'Security Group Rules', + 'Subnets', + 'Subnet Pools' + ) + return (column_headers, + (utils.get_dict_properties( + s, columns, + ) for s in result)) + + return ((), ()) + class SetQuota(command.Command): - """Set quotas for project or class""" + _description = _("Set quotas for project or class") def _build_options_list(self): if self.app.client_manager.is_network_endpoint_enabled(): @@ -165,7 +338,7 @@ class SetQuota(command.Command): **volume_kwargs) if network_kwargs: sys.stderr.write("Network quotas are ignored since quota class" - "is not supported.") + " is not supported.") else: project = utils.find_resource( identity_client.projects, @@ -186,7 +359,7 @@ class SetQuota(command.Command): class ShowQuota(command.ShowOne): - """Show quotas for project or class""" + _description = _("Show quotas for project or class") def get_parser(self, prog_name): parser = super(ShowQuota, self).get_parser(prog_name) @@ -273,6 +446,10 @@ class ShowQuota(command.ShowOne): volume_quota_info = self.get_compute_volume_quota(volume_client, parsed_args) network_quota_info = self.get_network_quota(parsed_args) + # NOTE(reedip): Remove the below check once requirement for + # Openstack SDK is fixed to version 0.9.12 and above + if type(network_quota_info) is not dict: + network_quota_info = network_quota_info.to_dict() info = {} info.update(compute_quota_info) |
